Spring Reactor onErrorContinue not working - java

As per documentation I am expecting onErrorContinue will ignore the error element and continue the sequence. Below test case is failing with exception
java.lang.AssertionError: expectation "expectNext(12)" failed (expected: onNext(12); actual: onError(java.lang.RuntimeException:
#Test
public void testOnErrorContinue() throws InterruptedException {
Flux<Integer> fluxFromJust = Flux.just(1, 2,3,4,5)
.concatWith(Flux.error(new RuntimeException("Test")))
.concatWith(Flux.just(6))
.map(i->i*2)
.onErrorContinue((e,i)->{
System.out.println("Error For Item +" + i );
})
;
StepVerifier
.create(fluxFromJust)
.expectNext(2, 4,6,8,10)
.expectNext(12)
.verifyComplete();
}

onErrorContinue() may not be doing what you think it does - it lets upstream operators recover from errors that may occur within them, if they happen to support doing so. It's a rather specialist operator.
In this case map() does actually support onErrorContinue, but map isn't actually producing an error - the error has been inserted into the stream already (by way of concat() and the explicit Flux.error() call.) In other words, there's no operator producing the error at all, so there's therefore nothing for it to recover from, as an element supplied is erroneous.
If you changed your stream so that map() actually caused the error, then it would work as expected:
Flux.just(1, 2,3,4,5)
.map(x -> {
if(x==5) {
throw new RuntimeException();
}
return x*2;
})
.onErrorContinue((e,i)->{
System.out.println("Error For Item +" + i );
})
.subscribe(System.out::println);
Produces:
2
4
6
8
Error For Item +5
An alternative depending on the real-world use case may be to use onErrorResume() after the element (or element source) that may be erroneous:
Flux.just(1, 2, 3, 4, 5)
.concatWith(Flux.error(new RuntimeException()))
.onErrorResume(e -> {
System.out.println("Error " + e + ", ignoring");
return Mono.empty();
})
.concatWith(Flux.just(6))
.map(i -> i * 2)
.subscribe(System.out::println);
In general, using another "onError" operator (such as onErrorResume()) is generally the more usual, and more recommended approach, since onErrorContinue() is dependent on operator support and affects upstream, not downstream operators (which is unusual.)

Related

how to handle errors in RxJava?

It may sound funny or maybe novice, but I don't understand what it means that something "failed" in Reactive Programming. Do you mean a null? An empty object? someone could please give me examples.
Some of the scenarios I wish to realize are:
Assuming I send a query parameter and get either a listing with values or an empty listing; and lastly, I do not send the query parameter.
If an empty listing is issued I want to return an exception and a 404 status code.
If they do not send the query parameter I want to return an exception and some status code.
And of course, if a list with values is found, return it. Will it be possible to make these cases in a single method? how do I do it?
First, a reactor operator can ends in different ways:
Completes successfully after emitting one (Mono) or more (Flux) value(s)
Completes empty: The pipeline sends completion signal, but no value has been emitted
Completes in error: somewhere in the pipeline, an error happened. By default, as in imperative code, it stops the chain of operations, and is propagated.
Cancelled: the pipeline might be interrupted by a manual action or a system shutdown. It then ends in error (a special kind of error, but an error nonetheless)
Secondly, reactive-stream, whatever the implementation (RxJava or Reactor) does not accept null values. It means that trying to produce a null value in/from a reactive stream will either cause an error or an undefined behavior. This is stated in reactive-stream specification, rule 2.13:
Calling [...] onNext [...] MUST return normally except when any provided parameter is null in which case it MUST throw a java.lang.NullPointerException to the caller
Let's try to produce some simple examples first.
This program shows the possible ways a pipeline can complete:
// Reactive streams does not accept null values:
try {
Mono.just(null);
} catch (NullPointerException e) {
System.out.println("NULL VALUE NOT ACCEPTED !");
}
// Mono/Flux operations stop if an error occurs internally, and send it downstream
try {
Mono.just("Something")
.map(it -> { throw new IllegalStateException("Bouh !"); })
.block();
} catch (IllegalStateException e) {
System.out.println("Error propagated: "+e.getMessage());
}
// A mono or a flux can end "empty". It means that no value or error happened.
// The operation just finished without any result
var result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
// Materialize allow to receive the type of signal produced by the pipeline (next value, error, completion, etc.)
.materialize()
.block();
System.out.println("Input value has been filtered out. No 'next' value " +
"received, just 'completion' signal:" + result.getType());
Its output:
NULL VALUE NOT ACCEPTED !
Error propagated: Bouh !
Input value has been filtered out. No 'next' value received, just 'completion' signal:onNext
Then, let's look at a program that intercept empty pipelines and errors, and handle them gracefully:
// Errors can be intercepted and replaced by a value:
var result = Mono.error(new IllegalStateException("No !"))
.onErrorResume(err -> Mono.just("Override error: Hello again !"))
.block();
System.out.println(result);
// Empty pipelines can also be replaced by another one that produce a value:
result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
.switchIfEmpty(Mono.just("Override empty: Hello again !"))
.block();
System.out.println(result);
It produces:
Override error: Hello again !
Override empty: Hello again !
With all this tools, we can solve the problem you describe with your query.
Let's mimic it:
public static Flux<String> processRequest(String queryParam) {
if (queryParam == null || queryParam.isEmpty()) return Flux.error(new IllegalArgumentException("Bad request"));
return Mono.just(queryParam)
.flatMapMany(param -> Flux.fromArray(param.split("_")))
.switchIfEmpty(Mono.error(new IllegalStateException("No data")));
}
public static void main(String[] args) {
String[] inputs = { null, "hello_world", "___" };
for (var input : inputs) {
try {
String result = processRequest(input)
.collect(Collectors.joining(", ", "[", "]"))
.block();
System.out.println("COMPLETED: " + result);
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
}
It prints:
ERROR: Bad request
COMPLETED: [hello, world]
ERROR: No data

How to handle nested subscriptions in Spring webflux

I have a requirement in which I need to perform a sequence of 3 method calls each of which returns a Mono.
Mono<WeightResponse> weightService()
Mono<PriceResponse> priceService(WeightResponse wr)
Mono<DispatchResponse> dispatchService(PriceResponse pr)
These 3 method calls need to be done in chronological order.
This is what I have tried to come up with. I am yet to compete and test this functionality end to end. I am looking for advice on how to handle this type of scenario in Spring Reactor?
There is subscription inside subscription inside subscription. Is this the right approach to handle such a scenario?
Could there be any side effects of such nested subscriptions?
weightService().subscribe(wr -> {
priceService(wr).subscribe (pr -> {
dispatchService(pr).subscribe (dr -> {
System.out.println("Dispatch Done!");
},
e -> log.error("error in dispatch {}", e);
);
},
e -> log.error("error in pricing {}", e);
);
},
e -> log.error("error in weight calculation {}", e);
);
You should not subscribe explicitly. Typically you need to construct reactive flow and the framework like spring-webflux will subscribe to it.
In the following example flatMap would subscribe internally and you could chain response to use it in the next operator.
Mono<DispatchResponse> execute() {
return weightService()
.flatMap(wr -> priceService(wr))
.flatMap(pr -> dispatchService(pr));
}

Stream supplier getting error 'stream has already been operated upon or closed'

Even though I am using Supplier for my streams and using Supplier.Get() each time I want to retrieve my strem and perform a terminal operation on it, I am still getting the "stream has already been operated upon or closed" exception. Could anyone please look at my code and suggest what I am doing wrong?
Method where exception is being thrown:
private static void printMyDetails(Supplier<Stream<Result>> mySupplier, String myStatus) {
checkNotNull(mySupplier);
checkArgument(isNotEmpty(myStatus), "Invalid My status");
if (mySupplier.get().noneMatch(result -> true)) { //<-This is where the exception is thrown
if (log.isInfoEnabled()) {
log.info("Number of My with status '{}': {}", My, 0);
}
} else {
log.info("Number of My with status '{}': {}", myStatus, mySupplier.get().count());
log.info("Details of My(s) with status: '{}'", myStatus);
mySupplier.get().sorted().forEach(Utils::printMyNameAndDetails);
}
}
Place which is calling the above method:
rb.keySet().stream().filter(key -> containsIgnoreCase(key, "status")).map(rb::getString)
.filter(StringUtils::isNotEmpty).forEach(status -> {
var resultsWithExpectedStatusSupplier = requireNonNull(getResultsWithExpectedStatusSupplier(results, status));
resultsWithExpectedStatusSupplier.ifPresentOrElse(streamSupplier -> printMyDetails(streamSupplier, status), () -> {
if (log.isInfoEnabled())
log.info("0 My with status: {}", status);
});
});
The stream supplier:
private static Optional<Supplier<Stream<Result>>> getResultsWithExpectedStatusSupplier(
#NotNull List<Result> results, #NotNull String expectedStatus) {
checkArgument(!results.isEmpty(), "Results list is empty");
checkArgument(isNotEmpty(expectedStatus), "Invalid expected status");
var resultStreamWithExpectedStatus = requireNonNull(results.stream().filter(result -> ofNullable(result).map(Result::getStatus)
.allMatch(s -> isNotEmpty(s) && s.equalsIgnoreCase(expectedStatus))));
return resultStreamWithExpectedStatus.count() == 0 ? Optional.empty() : Optional.of(() -> resultStreamWithExpectedStatus);
}
The general problem is as Christian Ullenboom said: The stream has already been consumed. The exact location in your code is the call to resultStreamWithExpectedStatus.count() in the method getResultsWithExpectedStatusSupplier, as Stream.count is a reduction/terminal operation which consumes the stream.
As stated in e.g. this answer, you cannot get the streams size without consuming it. Fix it by storing the filtered items in a collection (e.g. Collectors.toList), querying the size there and returning the collection itself rather than the stream?
On a side note, I think you misuse Optional a bit too much. The code could be simpler passing empty streams (or even better: pass an empty, filtered collection).
You can consume a Stream just once. It looks like the Supplier is always giving the same Stream again and again. After the first terminal operation the Stream is drained; the Stream from the Supplier has to be a new Stream all the time.

Java Reactor onComplete inconsistency

I'm sure that I'm just missing something. I'm running the following code:
#Test
public void simpleCreation() throws Exception {
Iterator<String> data = ImmutableList.of("1", "2", "3").iterator();
Flux<String> stringFlux = Flux.create(emmiter -> {
while ( data.hasNext() ) {
emmiter.next(data.next());
}
emmiter.complete();
});
ConnectableFlux<String> connectableFlux = stringFlux.publish();
connectableFlux.doOnComplete(() -> System.out.println("connectableFlux.doOnComplete"));
stringFlux.doOnComplete(() -> System.out.println("stringFlux.doOnComplete"));
CountDownLatch completeLatch = new CountDownLatch(1);
Disposable disposable = connectableFlux.subscribe(s -> {
System.out.println("subscribe: data: " + s);
}, error -> { }, completeLatch::countDown);
connectableFlux.connect();
completeLatch.await();
disposable.dispose();
}
and expect it to print either "connectableFlux.doOnComplete" or "stringFlux.doOnComplete" or both, but I see neither. OnComplete callback from subscribe method is executed with no problem, but neither of these methods called and I do not quite see why.
For me it looks slightly inconsistent - in one place callback is called and others are just ignored. I can observe the similar behaviour with doOnNext.
I would appreciate if someone can explain the concept behind that. I'm sure that is not bug, but just something I'm missing about the framework or the concept in general.
This line is causing the problem:
connectableFlux.doOnComplete(() -> System.out.println("connectableFlux.doOnComplete"));
The result of the call to doOnComplete() is ignored. The method returns a new version of the Flux instance on which you want to call subscribe(), it does not add the logic to the old connectableFlux instance.
Try it like this:
Iterator<String> data = ImmutableList.of("1", "2", "3").iterator();
Flux<String> stringFlux = Flux.create(emmiter -> {
while (data.hasNext()) {
emmiter.next(data.next());
}
emmiter.complete();
});
stringFlux.doOnComplete(() -> System.out.println("stringFlux.doOnComplete()"))
.subscribe(s -> System.out.println("subscribe: data: " + s), error -> {})
.dispose();
stringFlux.publish().connect();
I can't add this as a comment, so sorry about bumping an old question. Just wanted to share this bit of the official Reactor guide:
B.2. I used an operator on my Flux but it doesn’t seem to apply. What gives?
Make sure that the variable you .subscribe() to has been affected by the operators you think should have been applied to it.
Reactor operators are decorators. They return a different instance that wraps the source sequence and add behavior. That is why the preferred way of using operators is to chain the calls.
Compare the following two examples:
without chaining (incorrect)
Flux<String> flux = Flux.just("foo", "chain");
flux.map(secret -> secret.replaceAll(".", "*"));
flux.subscribe(next -> System.out.println("Received: " + next));
The mistake is here. The result isn’t attached to the flux variable.
without chaining (correct)
Flux<String> flux = Flux.just("foo", "chain");
flux = flux.map(secret -> secret.replaceAll(".", "*"));
flux.subscribe(next -> System.out.println("Received: " + next));
This sample is even better (because it’s simpler):
with chaining (best)
Flux<String> secrets = Flux
.just("foo", "chain")
.map(secret -> secret.replaceAll(".", "*"))
.subscribe(next -> System.out.println("Received: " + next));
The first version will output:
Received: foo
Received: chain
Whereas the two other versions will output the expected:
Received: ***
Received: *****
https://projectreactor.io/docs/core/release/reference/#faq.chain

How can I return an RxJava Observable which is guaranteed not to throw OnErrorNotImplementedException?

I want to create a pattern in my application where all Observable<T> objects that are returned have some default error handling, meaning that the subscribers may use the .subscribe(onNext) overload without fear of the application crashing. (Normally you'd have to use .subscribe(onNext, onError)). Is there any way to acheive this?
I've tried attaching to the Observable by using onErrorReturn, doOnError and onErrorResumeNext - without any of them helping my case. Maybe I'm doing it wrong, but I still get rx.exceptions.OnErrorNotImplementedException if an error occurs within the Observable.
Edit 1: This is example of an Observable that emits an error, which I want to handle in some middle layer:
Observable.create(subscriber -> {
subscriber.onError(new RuntimeException("Somebody set up us the bomb"));
});
Edit 2: I've tried this code to handle the error on behalf of the consumer, but I still get OnErrorNotImplementedException:
// obs is set by the method illustrated in edit 1
obs = obs.onErrorResumeNext(throwable -> {
System.out.println("This error is handled by onErrorResumeNext");
return null;
});
obs = obs.doOnError(throwable -> System.out.println("A second attempt at handling it"));
// Consumer code:
obs.subscribe(
s -> System.out.println("got: " + s)
);
This will work - the key was to return Observable.empty();
private <T> Observable<T> attachErrorHandler(Observable<T> obs) {
return obs.onErrorResumeNext(throwable -> {
System.out.println("Handling error by printint to console: " + throwable);
return Observable.empty();
});
}
// Use like this:
Observable<String> unsafeObs = getErrorProducingObservable();
Observable<String> safeObservable = attachErrorHandler(unsafeObs);
// This call will now never cause OnErrorNotImplementedException
safeObservable.subscribe(s -> System.out.println("Result: " + s));

Categories