The RxJava2 Default Error Handler

Or: “why is RxJava crashing my application when I have an onError callback?”

We are all good developers and handle onError on our RxJava streams. That means that our streams are all crash-proof, right?

Wrong.

We will look at a few examples in which our applications can crash due to an RxJava stream throwing an exception even if we have implemented onError correctly. But first, let’s look at what these types of issues look like.

The global error handler

RxJavaPlugins.onError is RxJava’s global error handler for any exceptions that can’t be delivered to a subscriber. By default, errors sent to the global error handler will be thrown, and thus your application will crash.

The release notes for RxJava 2.0.6 summarize this behavior (and the reasoning behind it):

One of the design goals of 2.x was that no errors can be lost. Sometimes, the sequence ends or gets cancelled before the source could emit an onError which has nowhere to go at that point and gets routed to the RxJavaPlugins.onError.

If RxJava didn’t have a global error handler, these exceptions would occur silently, and you would be blissfully ignorant of potential issues in your code.

As of RxJava 2.0.6, RxJavaPlugins.onError tries to be smart and separate library/implementation bugs from situations in which an error is undeliverable. It will leave exceptions categorized as “bugs” alone and rethrow those exceptions as-is. RxJava wraps any other exceptions in an UndeliverableException and throws that. You can see the full logic for determining whether an exception is a bug here.

One of the most common exceptions that developers who are new to RxJava encounter is OnErrorNotImplementedException. This is the familiar exception that you receive if an observable throws an exception and you have not implemented onError in your subscriber. OnErrorNotImplementedException is one of the “bug” cases that RxJava does not wrap in an UndeliverableException, since it generally means that you simply forgot to add an onError handler but the observable does have a subscriber.

UndeliverableException

Since most of the “bug” cases are fairly easy to spot and correct, we won’t spend any more time looking at them. The exceptions that RxJava wraps in an UndeliverableException are a little more interesting because it might not be obvious why the exception can’t be delivered.

As the name may imply, RxJava throws an UndeliverableException when it cannot deliver an onError signal. The cases in which this can happen vary based on what your observables and subscribers are doing, and we will look at two examples shortly. For now, suffice it so say that an error signal is undeliverable if there is no subscriber that RxJava can deliver the error to.

Example one: zipWith()

The first scenario in which I encountered an UndeliverableException was with the zipWith operator. Consider the following code:

We zip together two observables that will each emit anonError signal. What do we expect to happen? We might assume that our onError will be twice. However, the reactive streams specification states:

Once a terminal state has been signaled (onError, onComplete) it is REQUIRED that no further signals occur.

Once our onError is called, it can’t be called a second time. What happens to the second error signal? It is delivered to RxJavaPlugin.onError.

One easy way to get into this situation is to zip together two observables that perform network operations (e.g. observable Retrofit calls). If both calls fails (perhaps because there is no internet connection), both observables will emit errors. One error will go to your subscriber’s onError and the other will go to RxJavaPlugins.onError.

Example two: ConnectableObservable with no subscriber

ConnectableObservable can also be a source of anUndeliverableException. Remember that a ConnectableObservable will emit items if it is connected, regardless of whether or not there are any subscribers. If the ConnectableObservable attempts to emit an error signal when it has no subscribers, that error will go to the global error handler.

Here’s an example of some fairly innocent-looking code that can be problematic:

If the someApi.retrofitCall() emits an error (e.g. because the user has no internet connection), the application will crash because the network error will be delivered to the RxJava global error handler.

This might seem like a contrived example, but it can be easy to get into a situation in which your ConnectableObservable is still connected but has no subscribers. I ran into this with autoConnect() on an API call. autoConnect() does not automatically disconnect the source observable. I unsubscribed from the observable when my Activity was stopped, but if the API call later came back with an error the application would crash with an UndeliverableException.

Handling the errors

So how do we deal with these exceptions?

The first step if you are seeing these types of crashes is to identify what is causing them. Ideally you want to fix exceptions that are sent to the global RxJava error handler at their source.

One solution for the zipWith example above is to take one (or both) of the observables and apply one of the error handling operators. For example, you could use onErrorReturn to provide a default value.

The ConnectableObservable example is easy to fix- just make sure to disconnect when your subscribers unsubscribe. autoConnect for example has an override that takes in a function that can handle the connection appropriately (see more about autoConnect here).

Another option for handling errors sent to the global error handler is to set your own error handler. RxJavaPlugins.setErrorHandler(Consumer<Throwable>) is a hook for doing just that. If it is appropriate for your use case, you can use this intercept all errors sent to RxJavaPlugins.onError and handle them as you see fit. This is fairly heavy handed though- remember that this is a global handler for all the RxJava streams in your application.

Finally, if you are create()ing your Observables, you can call emitter.tryOnError() instead of emitter.onError(). This will only emit the error if the stream hasn’t terminated and is still subscribed to. Note that this method is experimental though.

The moral of the story is that you cannot assume that having an onError callback will make your RxJava streams crash-proof. You need to be aware of situations in which error signals might not be deliverable to a subscriber, and make sure to handle those scenarios gracefully.

Like what you read? Give Bryan Herbst a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.