I don't think there should be much difference in handling runtime (aka unchecked) or regular (aka checked) exceptions. Both are widely used these days and could be either recoverable or not depending on particular situation.
The reactive ways of handling errors are:
- via
onErrorResumeNext
or onErrorReturn
operators; these allow to inspect the error and possibly recover from it
- via
retry*
family of operators; these allow to inspect the error and possibly recover from it by means of re-subscribing (for example retry network call)
- via
onError
callback of your subscribers; by the way in case you do not supply such a callback the error will be re-thrown in regular Java fashion, so your program will crash
Related topic: How to handle different kinds of errors in Retrofit Rx onError without ugly instanceof
Also note the drawbacks of throwing exceptions in regular Java way:
- the call stack during message processing is different from the call stack when you define message processing rules; this means it could be quite hard to catch such exceptions, as well as to interpret the stack trace
- exceptions caught by schedulers might not lead to program termination; i.e. your program might end up hanging in broken state
Sample code
Observable.fromCallable(() -> {
...
if (ok) return "Success!";
else throw new RuntimeException("Failure at source");
})
.map(s -> {
... processing is bypassed in case of an error
})
.map(s -> {
...
if (...) return s.upperCase();
else throw new RuntimeException("Failure during processing");
})
.onErrorReturn(e -> {
if (e.getMessage().contains("processing"))
return "Recovered";
throw Exceptions.propagate(e); // let it continue as an error
})
.subscribe(s -> println("got result: " + s),
e -> println("got error: " + e);
All exceptions get caught by RxJava and passed along the defined route.
onError*
operators act like intermediate catch
blocks.
Subscriber's onError
callback acts like top-level catch
block.
More links on the subject: