I was testing my code for the error handling part.Looks like its not working as expected.I have broke down the code snippet as show below.Overall addenda is to retry 5 time when there is exception.For simplicity I have written a method to throw NPE exception and added a error handler.Can some one explain what is wrong.
public static void main(String[] args) {
Mono.just( errorDemo() )
.retry(5)
.doOnError( e -> log.error( "Error {}", e.getStackTrace() ) )
.doOnSuccess( e -> log.info( "done" ) );
}
public static Mono<Void> errorDemo() {
return Mono.error( NullPointerException::new ); // throwing back
exception to calling method
}
You should subscribe your Mono. Nothing happens if you don't subscribe.
You could add .block() for your example.
Related
I'm adapting some sample code from what3words for accessing their API via their Java SDK. It uses RXJava.
The sample code is:
Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
});
First of all. this gives a deprecation warning when building and a IDE warning (Result of 'Observable.subscribe()' is ignored).
To resolve this first issue I have added Disposable myDisposable = in front of the Observable. Is this correct? (See below for where it is added)
Next I need to add a timeout so that I can show a warning etc if the request times out. To do this I have added .timeout(5000, TimeUnit.MILLISECONDS) to the builder.
This works, but the way timeouts seem to work on Observables is that they throw an exception and I cannot figure out how to catch and handle that exception.
What I have right now is:
Disposable myDisposable = Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(5000, TimeUnit.MILLISECONDS)
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
});
This builds and runs fine, and the API/deprecation warning is not shown, BUT when no network is available this correctly times out and throws the unhandled exception.
So, the code seems to be correct, but how on earth do add the exception handling to catch the timeout TimeoutException that is thrown?
I've tried numerous things, including: adding a try-catch clause around the whole Observable - this warns that TimeoutException is not thrown by the code in the `try; and adding an error handler.
Adding the error handler has got me closest, and so the code below is as far as I have got:
Disposable myDisposable = Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(5000, TimeUnit.MILLISECONDS)
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
}, error -> {
runOnUiThread(new Runnable() {
#Override
public void run() {
myTextView.setText(R.string.network_not_available);
}
});
});
This catches the Timeout correctly and updates my UI without error, however when the network is restored it seems that the Observable might be trying to return and a null pointer exception is thrown.
(Update, this NPE might actually be being thrown sometimes after a short time whether the network is restored or not... but it is always thrown when the network restores.)
I get FATAL EXCEPTION: RxCachedThreadScheduler-1 and java.lang.NullPointerException: Callable returned a null value. Null values are generally not allowed in 3.x operators and sources.
Do I need to destroy the Observable or something to prevent the NPE?
You need to add an onError handler to your subscribe call:
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
},
error -> {
// handle error here
});
When a an exception makes it to a subscribe call that does not have an onError handler, it will throw a OnErrorNotImplementedException, like this:
io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | java.util.concurrent.TimeoutException: The source did not signal an event for 1 seconds and has been terminated.
Adding the onError handler will prevent that, and the onError handler will get called instead.
There's a few things going on here:
First of all. this gives a deprecation warning when building and a IDE warning (Result of 'Observable.subscribe()' is ignored).
subscribe() returns a Disposable. The idea is that when you're no longer interested in receiving the output of your observable, you call dispose() on the disposable and the work terminates. This can also prevent memory leaks.
As an example, imagine you have an Activity, and you start an Observable to run a long network query which finally posts something to the Activity UI. If the user navigates away before this task completes, or the Activity is otherwise destroyed, then you're no longer interested in its output because there is no longer a UI to post to. So you may call dispose() in onStop().
So, the code seems to be correct, but how on earth do add the exception handling to catch the timeout TimeoutException that is thrown?
Using the error block in subscribe is one option, but there are others. For example, if you wanted to keep using your Result class, you could use something like onErrorReturn(throwable -> Result.error(throwable)). Obviously I'm guessing what that class looks like:
.timeout(5000, TimeUnit.MILLISECONDS)
.onErrorReturn(throwable -> Result.errorWithMessage(R.string.network_not_available))
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
myTextView.setText(result.getErrorMessage());
}
});
java.lang.NullPointerException: Callable returned a null value. Null values are generally not allowed in 3.x operators and sources.
This:
wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute()
is returning null. You can do something like:
Observable.fromCallable(() -> {
Result<?> out = wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute();
if(out == null)
out = Result.error(/*Returned null*/);
}
return out;
}
I have a code snippet like this
public ListenableFuture<ProductCatalog> someAsync(List<someItem> someItemList) throws Exception {
return ProductCatalog.someFunction()
.executeAsync();
}
Now I want to log the header I will get from ProductCatalog. I know this is a asyncFunction and program will not wait for its response. But the problem is that I have to log the header without changing the method definition. Will this below execution do the trick or any otherway. I am new to this async functionality in java.
public ListenableFuture<ProductCatalog> someAsync(List<someItem> someItemList) throws Exception {
ListenableFuture<ProductCatalog> Lp = ProductCatalog.someFunction().executeAsync();
ProductCatalog productCatalog = Lp.get();
productCatalog.getHeader();
return Lp;
}
When you're calling get() on a Future execution will block until the result is ready or execution has failed so this will break your call (i.e. it won't be async anymore).
However, the prefix "Listenable" indicates you can add listeners on the future, e.g. like this:
Lp.addCallback(product -> log(product.getHeader()), error -> log(error));
This makes use of lambdas which basically implement the SuccessCallback and FailureCallback interfaces.
Your method would thus look like this:
public ListenableFuture<ProductCatalog> someAsync(List<someItem> someItemList) throws Exception {
ListenableFuture<ProductCatalog> Lp = ProductCatalog.someFunction().executeAsync();
Lp.addCallback(product -> log(product.getHeader()), error -> log(error));
return Lp;
}
I have been tried to find out why the Runtime exception is not propagated back to the client. I have the next piece of code, so when I return a Mono.error this should be handled in the subscribe error section, to throw an exception to the client, but this is not happening. Any idea about I am doing wrong?
public void onmethod(EventDetails eventDetails, String eventType) {
messageConverter.convertAndSendMessage(eventType, eventDetails)
.flatMap(aBoolean -> {
if (aBoolean)
log.debug("Event published");
else {
log.debug("Problem publishing event.");
return Mono.error(new RuntimeException("Problem publishing event."));
}
return Mono.just(true);
})
.doOnError(throwable -> log.error("Failed to consume message", throwable))
.subscribe(
next -> { } ,
error -> {
throw Exceptions.propagate(error);
}
);
}
And this is the test I have to verify the method behaviour. This test fails as any exception is thrown. However, I can see in the logs that the exception happens.
Assertions.assertThrows(RuntimeException.class, () ->
consentsListener.onmethod(
eventDetails, "eventType")
);
19:44:32.463 [main] ERROR events.auth.ConsentsListenerImpl - Failed to consume message
java.lang.RuntimeException: Problem publishing event.
at events.auth.ConsentsListenerImpl.lambda$publishMessage$0(ConsentsListenerImpl.java:121)
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:152)
at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
at reactor.core.publisher.Mono.subscribe(Mono.java:4084)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4214)
at reactor.core.publisher.Mono.subscribe(Mono.java:4070)
at reactor.core.publisher.Mono.subscribe(Mono.java:4006)
at reactor.core.publisher.Mono.subscribe(Mono.java:3978)
at ...
org.opentest4j.AssertionFailedError: Expected java.lang.RuntimeException to be thrown, but nothing was thrown.
Thank you very much in advance.
Best regards.
I have been tried to find out why the Runtime exception is not propagated back to the client.
Because you're subscribing to it, which is almost certainly the wrong thing to do. The framework (Webflux in this case) should be what controls the subscription to your publisher.
If you remove your subscribe() call on that chain, change your method to return Mono<Boolean> and then return the entire chain in that method, it should work as expected.
In the following method, I am purposely making the call c.rxCommit() throw an exception during test.
The exception is thrown as expected, but I never land in the onErrorResumeNext block as expected.
I am expecting to land inside here to handle the error and perform some rollback.
Can I get some advice as to why I am not landing within the error block? Thanks.
public Observable<Void> myMethod(Observable<Map<String, String>> records) {
return client.rxGetConnection()
.flatMapCompletable(c -> c.rxSetAutoCommit(false)
.toCompletable()
.andThen(records.flatMapSingle(record -> perform(c, record)).toCompletable())
.andThen(c.rxCommit().toCompletable()) // test makes this c.rxCommit() throw an error on purpose.
.onErrorResumeNext(throwable -> {
// I want to land in here when error but it is not happening.
return c.rxRollback().toCompletable();
})
.andThen(c.rxSetAutoCommit(true).toCompletable())
.andThen(c.rxClose()).toCompletable()
).toObservable();
}
The test
#Test
void test() {
when(sqlConnection.rxCommit()).thenThrow(new RuntimeException("Error on Commit")); // mockito
myClass.myMethod(mockedRecords).subscribe(
success -> System.out.println(),
throwable -> System.out.println("err " + throwable), // I land in here with the new RuntimeException("Error on Commit") exception.
// this is wrong. Error should have been handled in the onErrorResumeNext block and should be getting success here instead.
);
// some verify() to test outcome
}
As akarnokd says, your sqlConnection.rxCommit() mock is wrong, because it throws exception right after method call, not by subscription.
If want to receive error in rx flow, try this:
when(sqlConnection.rxCommit()).thenReturn(Single.error(RuntimeException("Error on Commit")));
But if you really want to throw exceptions in rxCommit(), try to wrap it in Single.defer:
.andThen(
Single.defer(() -> {
return c.rxCommit().toCompletable();
})
I am using io.projectreactor 3 (reactor-core 3.2.6.RELEASE) and I have noticed some discrepancies regarding error handling. Unfortunately, official documentation does not provide enough details to solve my problems.
I have following 4 snippets. In some cases exception will be ignored and in other cases it will thrown further. What is the way to actually produce and consume exceptions?
Snippet 1
In this case, exception will be ignored and main() will complete without receiving exception.
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
}).doOnNext(e -> {
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
Output:
DONE
Snippet 2
Is similar as example from above, except that we don't use Flux.push but Flux.just. Main() will receive exception.
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.just(
1
).doOnNext(e -> {
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
Output:
Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: HELLO WORLD
at Scratch.lambda$main$1(scratch_15.java:10)
...
Snippet 3
We signal exception by calling sink.error. Main() will not receive exception.
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
sink.error(new RuntimeException("HELLO WORLD"));
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
Output:
1
2
DONE
Snippet 4
We throw exception directly. Main() will receive exception.
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
Output
1
2
Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: HELLO WORLD
at Scratch.lambda$main$1(scratch_15.java:10)
...
What is the correct way to handle exception when working with reactive-core? The only reliable way seems not to use error callback at all, and instead surround flux.subscribe with try/catch. But in that case I always receive UnsupportedOperationException instead of original exception, and then I need to use Exceptions.isErrorCallbackNotImplemented to check if it comes from reactive, extract nested exception and then throw it.
This can be done of course, but it needs to be done consistently on every place where we use Flux is being subscribed. That does not looks nice to me. What I'm missing here?
In all your examples the problem is re-throwing from the .subscribe(...) error-handling lambda.
If you want the exception to be thrown in a main block, use block() variants.
If you want to test that the error is propagated throughout the pipeline, use StepVerifier.create(pipeline).expectError(...).verify().
.subscribe in general is to get notified of "terminal" state, not intended to recover or re-throw errors (use onError* operators upstream for that).
The just-based examples seem to correctly propagate the exception because they execute no user-provided code at subscription, so no try/catch is in place during subscribe(Consumer<Throwable>).
push, like generate/create/defer and compose, execute some user-defined logic (the Consumer<FluxSink>), at subscription. They guard against the whole Consumer throwing exceptions and try to propagate that (as an onError signal) rather than directly throwing it.
But if the failure in the Consumer is caused while executing one of the sink's methods, it can be problematic if the subscriber re-throws: we enter a recursion where calling the sink calls the sink. We protect against this infinite case by exiting when we detect recursive draining of the sink.
This is why push-based examples that trigger error after sink.next or in sink.error (examples 1 and 3) fail to produce the exception in the main:
Consumer is applied
sink.next is called and next operator creates exception 1, or sink.error is called
exception 1 reaches subscribe and is re-thrown as exception 2
this short-circuits the Consumer.apply, exception 2 is passed to sink.error
the sink is already being called so we break out to avoid infinite recursion
exception 2 is never seen
In example 4 on the other hand, we're no longer in the middle of calling the sink's methods, and the original exception doesn't reach the subscriber first:
Consumer is applied
it directly throws exception 1
this short-circuits the Consumer.apply and exception 1 is passed to sink.error
which propagates to the subscribe
which re-throws it as exception 2
exception 2 is seen in the main method