2

RuntimeExceptions are supposed to indicate programming error and I want my application to crash when something inside my observables throws RuntimeException.

What is the best way to do this? Right now I'm considering this solution (it's Kotlin, but I hope it's understandable)

fun <T> Observable<T>.subscribeCrashOnRuntimeException(onNext: (T) -> Unit, onError: (Throwable) -> Unit) {
  this.subscribe({
    onNext(it)
  }, { e ->
    if (e is RuntimeException) {
      throw e
    } else {
      onError(e)
    }
  })
}

fun usageExample() {
  val observable = Observable.just(1)
  observable.subscribeCrashOnRuntimeExceptions(
    { next -> Log.d("TAG", "next: $next") },
    { e -> Log.d("TAG", "error: $e") }
  )
}

But I have doubts about it. For example it is hard to occasionally "catch" specific RuntimeExceptions with this solution. Perhaps there is a well known way to deal with situation that I just don't know how to google?

Yaroslav Stavnichiy
  • 20,738
  • 6
  • 52
  • 55
Dmitry Ryadnenko
  • 22,222
  • 4
  • 42
  • 56
  • Read about `onErrorResumeNext` where you can decide what to do with an error. – akarnokd Dec 19 '16 at 14:10
  • `RuntimeExceptions are supposed to indicate programming error` - not necessarily. Is a missing file a programming error? Is broken network connection? Most of the times you explicitly do not want to crash, but mitigate or retry. – Tassos Bassoukos Dec 19 '16 at 14:12
  • @TassosBassoukos missing file is not a RuntimeExceptions the same is true for broken network connection. It seems like you don't understand what is RuntimeExceptions. It seems like you think it's just a synonym for Exception. – Dmitry Ryadnenko Dec 19 '16 at 14:26

1 Answers1

2

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:

  1. via onErrorResumeNext or onErrorReturn operators; these allow to inspect the error and possibly recover from it
  2. 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)
  3. 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:

Community
  • 1
  • 1
Yaroslav Stavnichiy
  • 20,738
  • 6
  • 52
  • 55
  • Say I have some observable which should return either data or error. For example observable that executes http request. Both of these return types are valid and subscriber should be able to react to both of them. So, as I understand "official" RxJava way to handle this situation is returning an "either data or error" object. And don't supply onError() callback, so app will crash on any unexpected error. Am I right? It feels like this approach will lead to a ton of boilerplate, but maybe I'm wrong, should try it I guess. – Dmitry Ryadnenko Jan 12 '17 at 11:12
  • @DmitryRyadnenko Observable that executes http request should either *return* response or *throw* exception, which will be caught by RxJava and routed down the line as an error, which will end up in `onError` callback of your subscriber. – Yaroslav Stavnichiy Jan 12 '17 at 11:21
  • This is how I'm doing things right now and I've ended up with the same boilerplate in each onError method. Like https://gist.github.com/rongi/3955d4946231c195d04adf110928a2ed This polluted my otherwise simple and clean subscribe code, so I've came with the solution described in the question. This made my subscriber code clean again. Have you had the same problem? Do you see this as a problem? How do you address this? – Dmitry Ryadnenko Jan 12 '17 at 12:20
  • @DmitryRyadnenko If error handler is the same for several subscribers then you can extract it into a common function or class. Or simply omit error handler, add `catch` at top level of your application. – Yaroslav Stavnichiy Jan 12 '17 at 12:36