How to get combined effects of whenComplete and thenCompose? - java

I am trying to come up with a CompletableFuture with the combined effects of whenComplete and thenCompose, specifically:
Returns a CompletionStage instead of just a result, similar to thenCompose.
Executes even when previous stage completes exceptionally, similar to whenComplete, and does not stop the exception from propagating.
This post is close to what I'm trying to achieve but I don't want to use handle which hides the exception. Thanks for any ideas.

I don't believe CompletionStage or CompletableFuture provides any single method for this. However, combining handle with thenCompose should do what you want, if I understand your requirements correctly.
A handle stage is executed whether the parent stage has completed normally or exceptionally and gives you access to the result or error, respectively. From this stage you could return another CompletionStage which would either be completed normally or exceptionally depending on what arguments the handle stage receives.
handle((T result, Throwable error) -> {
if (error != null) {
return CompletableFuture.<T>failedStage(error);
} else {
return processResult(result); // returns CompletionStage<T>
}
});
Now you have a CompletionStage<CompletionStage<T>>. Now we execute a flat map operation by invoking thenCompose:
thenCompose(Function.identity());
Which gives us a CompletionStage<T>. This CompletionStage<T> will be whatever instance was returned by handle. If that instance was a failed stage then the exception is still propagated; otherwise, the result is passed to whatever stage is dependent on the thenCompose stage and processing continues normally.
You can see this with the following example:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class Main {
public static void main(String[] args) {
methodThatReturnsCompletionStage()
.handle((result, error) -> {
if (error != null) {
return CompletableFuture.<String>failedStage(error);
} else {
return processResult(result);
}
})
.thenCompose(future -> {
System.out.println("#thenCompose invoked");
return future; // Identity function
})
.thenApply(result -> {
System.out.println("#thenApply invoked");
return result; // Identity function (exists to show intermediary stage)
})
.whenComplete((result, error) -> {
System.out.println("#whenComplete invoked");
if (error != null) {
error.printStackTrace(System.out);
} else {
System.out.println(result);
};
});
}
private static CompletionStage<String> methodThatReturnsCompletionStage() {
return CompletableFuture.completedStage("Hello");
// return CompletableFuture.failedStage(new RuntimeException("OOPS"));
}
private static CompletionStage<String> processResult(String result) {
return CompletableFuture.completedFuture(result + ", World!");
}
}
This will result in each stage being invoked and an output of Hello, World!. But if you switch methodThatReturnsCompletionStage() to return the failed stage instead then thenApply is skipped (because the future has failed) and the exception is given to whenComplete (which, like handle, is invoked for both normal or exceptional completion).
Note: Everything above uses the CompletionStage interface directly but using CompletableFuture works just as well (and may be preferable).

Related

Return a CompletableFuture without exposing executor thread

I expose a method in a library, which returns a CompletableFuture. That method's computation happens on a single-threaded Executor which is my bottleneck, hence I do not want any subsequent work to happen on the same thread.
If I use the simple approach of returning the result of "supplyAsync", I'll be exposing my precious thread to the callers, who may be adding synchronous operations (e.g. via thenAccept) which could take some CPU time on that thread.
Repro below:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CfPlayground {
private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);
CfPlayground() {}
private static void log(String msg) {
System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
}
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
void syncOp(String salutation) {
log("In syncOp: " + salutation);
}
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
public static void main(String[] args) throws InterruptedException {
CfPlayground compFuture = new CfPlayground();
compFuture.run();
Thread.sleep(500);
compFuture.preciousExecService.shutdown();
}
}
This indeed prints:
[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world
One solution I found was to introduce another Executor, and add a no-op thenApplyAsync with that executor before returning the CompletableFuture
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService).thenApplyAsync(s -> s, secondExecService);
}
This works, but doesn't feel super elegant - is there a better way to do this?
There is no feature to detach your completion from the execution of the dependent action. When the thread chaining the dependent action has already completed the registration and your executor’s thread completes the future, which other thread ought to execute the dependent action if no other executor was given?
Your approach of chaining another action with a different executor seems to be the best you can get. However, it’s important to note that in case of an exceptional completion, the exception gets propagated without evaluating functions passed to thenApply. This exception propagation could again lead to an exposure of the thread, if the caller chained an action like whenComplete, handle, or exceptionally.
On the other hand, you don’t need to specify a secondary executor, as you can use the async method without executor parameter, to get the default (common Fork/Join) pool.
So chaining .whenCompleteAsync((x,y) -> {}) is the best solution to your problem so far.
You could just change the method signature to return a Future instead of a CompletableFuture:
Future<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
That way, the run() method would throw a compilation error:
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
The caller would still be able to cast the returned Future back to a CompletableFuture, but that would be quite a misuse of your API and it cannot happen by accident.

Mono vs CompletableFuture

CompletableFuture executes a task on a separate thread ( uses a thread-pool ) and provides a callback function. Let's say I have an API call in a CompletableFuture. Is that an API call blocking? Would the thread be blocked till it does not get a response from the API? ( I know main thread/tomcat thread will be non-blocking, but what about the thread on which CompletableFuture task is executing? )
Mono is completely non-blocking, as far as I know.
Please shed some light on this and correct me if I am wrong.
CompletableFuture is Async. But is it non-blocking?
One which is true about CompletableFuture is that it is truly async, it allows you to run your task asynchronously from the caller thread and the API such as thenXXX allows you to process the result when it becomes available. On the other hand, CompletableFuture is not always non-blocking. For example, when you run the following code, it will be executed asynchronously on the default ForkJoinPool:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
It is clear that the Thread in ForkJoinPool that executes the task will be blocked eventually which means that we can't guarantee that the call will be non-blocking.
On the other hand, CompletableFuture exposes API which allows you to make it truly non-blocking.
For example, you can always do the following:
public CompletableFuture myNonBlockingHttpCall(Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
return uncompletedFuture;
}
As you can see, the API of CompletableFuture future provides you with the complete and completeExceptionally methods that complete your execution whenever it is needed without blocking any thread.
Mono vs CompletableFuture
In the previous section, we got an overview of CF behavior, but what is the central difference between CompletableFuture and Mono?
It worth to mention that we can do blocking Mono as well. No one prevents us from writing the following:
Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
Of course, once we subscribe to the future, the caller thread will be blocked. But we can always work around that by providing an additional subscribeOn operator. Nevertheless, the broader API of Mono is not the key feature.
In order to understand the main difference between CompletableFuture and Mono, lets back to previously mentioned myNonBlockingHttpCall method implementation.
public CompletableFuture myUpperLevelBusinessLogic() {
var future = myNonBlockingHttpCall();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
var errorFuture = new CompletableFuture();
errorFuture.completeExceptionally(new RuntimeException());
return errorFuture;
}
return future;
}
In the case of CompletableFuture, once the method is called, it will eagerly execute HTTP call to another service/resource. Even though we will not really need the result of the execution after verifying some pre/post conditions, it starts the execution, and additional CPU/DB-Connections/What-Ever-Machine-Resources will be allocated for this work.
In contrast, the Mono type is lazy by definition:
public Mono myNonBlockingHttpCallWithMono(Object someData) {
return Mono.create(sink -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
})
});
}
public Mono myUpperLevelBusinessLogic() {
var mono = myNonBlockingHttpCallWithMono();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
return Mono.error(new RuntimeException());
}
return mono;
}
In this case, nothing will happen until the final mono is subscribed. Thus, only when Mono returned by the myNonBlockingHttpCallWithMono method, will be subscribed, the logic provided to Mono.create(Consumer) will be executed.
And we can go even further. We can make our execution much lazier. As you might know, Mono extends Publisher from the Reactive Streams specification. The screaming feature of Reactive Streams is backpressure support. Thus, using the Mono API we can do execution only when the data is really needed, and our subscriber is ready to consume them:
Mono.create(sink -> {
AtomicBoolean once = new AtomicBoolean();
sink.onRequest(__ -> {
if(!once.get() && once.compareAndSet(false, true) {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
});
}
});
});
In this example, we execute data only when subscriber called Subscription#request so by doing that it declared its readiness to receive data.
Summary
CompletableFuture is async and can be non-blocking
CompletableFuture is eager. You can't postpone the execution. But you can cancel them (which is better than nothing)
Mono is async/non-blocking and can easily execute any call on different Thread by composing the main Mono with different operators.
Mono is truly lazy and allows postponing execution startup by the subscriber presence and its readiness to consume data.
Building up on Oleh's answer, a possible lazy solution for CompletableFuture would be
public CompletableFuture myNonBlockingHttpCall(CompletableFuture<ExecutorService> dispatch, Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
dispatch.thenAccept(x -> x.submit(() -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
}));
return uncompletedFuture;
}
Then, later on you simply do
dispatch.complete(executor);
That would make CompletableFuture equivalent to Mono, but without backpressure, I guess.

Unit test of method with CompletableFuture inside

I have method which in async way calls connector.runSomeService(data) and handles the response in method handleServiceResponse(res, node).
public void runServiceOnAllNodes(Collection<Node> nodes, Object data) {
nodes.parallelStream().forEach(node -> {
CompletableFuture<ResponseEntity> response = CompletableFuture
.supplyAsync(()-> connector.runSomeService(data));
response.exceptionally(ex -> {
log.error("OMG...OMG!!!")
return null;
})
.thenAcceptAsync(res -> handleServiceResponse(res, node));
});
}
private void handleServiceResponse(ResponseEntity res, Node node) {
if (res.isOK) {
node.setOKStatus();
} else {
node.setFailStatus();
}
dbService.saveNode(node);
}
Try to create unit test but when I try to verify if response is properly handled, the result of UT is non deterministic.
#Test
public void testRunServiceOnAllNodes() {
// given
List<Collector> nodes = Arrays.asList(node1, node2, node3);
when(connector.runSomeService(eq(node1), eq(data))).thenReturn(ResponseEntity.ok().body("{message:OK}"));
when(connector.runSomeService(eq(node2), eq(data))).thenReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""));
when(connector.runSomeService(eq(node3), eq(data))).thenThrow(new ResourceAccessException(""));
// when
engine.runServiceOnAllNodes(data, collectors);
// then
verify(connector, times(1)).runSomeService(eq(node1), eq(data));
verify(connector, times(1)).runSomeService(eq(node2), eq(data));
verify(connector, times(1)).runSomeService(eq(node3), eq(data));
verifyNoMoreInteractions(connector);
assertEquals(node1.getStatus(), "OK");
assertEquals(node2.getStatus(), "Fail");
}
It can end with a few different results eg.
Wanted but not invoked:
connector.runSomeService(node2);
However, there were other interactions with this mock:
connector.runSomeService(node1);
or
Argument(s) are different! Wanted:
connector.runSomeService(node1);
Actual invocation has different arguments:
connector.deployFileset(node2);
or sometimes it ends with success.
It is clear that the time of execution connector.runSomeService() and the time of the verification can interlace. The order of this two actions is not deterministic.
Using sleep sucks. Tried to gather all responses and calling future.get()
// when
engine.runServiceOnAllNodes(data, collectors);
for (CompletableFuture future : engine.getResponses()) {
future.get();
}
but I'm getting some exception but I still have the feeling that this way also sucks, isn't it?
I would suggest changing the runServiceOnAllNodes method to return a Future so your test, and, as a bonus, normal clients as well, can explicitly wait for the async behavior to finish.
public Future<Void> runServiceOnAllNodes(Collection<Node> nodes, Object data) {
return nodes.parallelStream().map(node -> {
CompletableFuture<ResponseEntity> response = CompletableFuture
.supplyAsync(()-> connector.runSomeService(data));
return response.exceptionally(ex -> {
LOGGER.error("OMG...OMG!!!");
return null;
})
.thenAcceptAsync(res -> handleServiceResponse(res, node));
})
.reduce(CompletableFuture::allOf).orElseGet(() -> CompletableFuture.completedFuture(null));
}
In your test, it is then simply a matter of calling get() on the future prior to making assertions and verifications.

RxJava: How to get all results AND errors from an Observable

I'm working on a project that involves Hystrix, and I decided to use RxJava. Now, forget Hystrix for the rest of this because I believe the main problem is with my complete screwing up of writing the Observable code correctly.
Need:
I need a way to return an observable that represents a number of observables, each running a user task. I want that Observable to be able to return all results from the tasks, even errors.
Problem:
Observable streams die on errors. If I have three tasks and the second task throws an exception, I never receive the third task even if it would have succeeded.
My Code:
public <T> Observable<T> observeManagedAsync(String groupName,List<EspTask<T>> tasks) {
return Observable
.from(tasks)
.flatMap(task -> {
try {
return new MyCommand(task.getTaskId(),groupName,task).toObservable().subscribeOn(this.schedulerFactory.get(groupName));
} catch(Exception ex) {
return Observable.error(ex);
}
});
}
Given that MyCommand is a class that extends HystrixObservableCommand, it returns an Observable and so shouldn't figure in on the problems I'm seeing.
Attempt 1:
Used Observable.flatMap as above
Good: Each Command is scheduled on it's own thread and the tasks run asynchronously.
Bad: On first Command exception, Observable completes having emitted previous successful results and emitting the Exception. Any in-flight Commands are ignored.
Attempt 2:
Used Observable.concatMapDelayError instead of flatMap
Bad: For some reason, tasks run synchronously. Why??
Good: I get all the successful results.
~Good: OnError gets a Composite exception with a list of the exceptions thrown.
Any help will be greatly appreciated and probably result in me being very embarrassed for not having thought of it myself.
Additional Code
This test succeeds with Observable.flatMap, but fails when using Observable.concatMapDelayError because the tasks do not run asynchronously:
java.lang.AssertionError: Execution time ran over the 350ms limit: 608
#Test
public void shouldRunManagedAsyncTasksConcurrently() throws Exception {
Observable<String> testObserver = executor.observeManagedAsync("asyncThreadPool",getTimedTasks());
TestSubscriber<String> testSubscriber = new TestSubscriber<>();
long startTime = System.currentTimeMillis();
testObserver.doOnError(throwable -> {
System.out.println("error: " + throwable.getMessage());
}).subscribe(testSubscriber);
System.out.println("Test execution time: "+(System.currentTimeMillis()-startTime));
testSubscriber.awaitTerminalEvent();
long execTime = (System.currentTimeMillis()-startTime);
System.out.println("Test execution time: "+execTime);
testSubscriber.assertCompleted();
System.out.println("Errors: "+testSubscriber.getOnErrorEvents());
System.out.println("Results: "+testSubscriber.getOnNextEvents());
testSubscriber.assertNoErrors();
assertTrue("Execution time ran under the 300ms limit: "+execTime,execTime>=300);
assertTrue("Execution time ran over the 350ms limit: "+execTime,execTime<=350);
testSubscriber.assertValueCount(3);
assertThat(testSubscriber.getOnNextEvents(),containsInAnyOrder("hello","wait","world"));
verify(this.mockSchedulerFactory, times(3)).get("asyncThreadPool");
}
Tasks for the above unit test:
protected List<EspTask<String>> getTimedTasks() {
EspTask longTask = new EspTask("helloTask") {
#Override
public Object doCall() throws Exception {
Thread.currentThread().sleep(100);
return "hello";
}
};
EspTask longerTask = new EspTask("waitTask") {
#Override
public Object doCall() throws Exception {
Thread.currentThread().sleep(150);
return "wait";
}
};
EspTask longestTask = new EspTask("worldTask") {
#Override
public Object doCall() throws Exception {
Thread.currentThread().sleep(300);
return "world";
}
};
return Arrays.asList(longTask, longerTask, longestTask);
}
You can use Observable.onErrorReturn(), and return special value (e.g. null), then filter non-special values downstream. Keep in mind that source observable will complete on error. Also depending on use case Observable.onErrorResumeNext()methods can be useful aswell. If you are interested in error notifications, use Observable.materialize(), this will convert items and onError(), onComplete() into Notifications, which then can be filtered by Notification.getKind()
Edit.
All operators mentioned above should be added right after .toObservable().subscribeOn(this.schedulerFactory.get(groupName)); assuming try/catch was absent.
You want to use mergeDelayError:
public <T> Observable<T> observeManagedAsync(String groupName,List<EspTask<T>> tasks) {
return Observable.mergeDelayError(Observable
.from(tasks)
.map(task -> {
try {
return new MyCommand(task.getTaskId(),groupName,task).toObservable().subscribeOn(this.schedulerFactory.get(groupName));
} catch(Exception ex) {
return Observable.error(ex);
}
}));
}
Note that your MyCommand constructor should not throw any exceptions; this allows your code to be written more concisely:
public <T> Observable<T> observeManagedAsync(String groupName,List<EspTask<T>> tasks) {
return from(tasks)
.map(task -> new MyCommand(task.getTaskId(), groupName, task)
.toObservable()
.subscribeOn(this.schedulerFactory.get(groupName)))
.compose(Observable::mergeDelayError);
}
Keep in mind that this will still invoke onError at most once; if you need explicit handling of all errors, use something like an Either<CommandResult, Throwable> as the return type (or handle the errors and return an empty observable).
Use .materialize() to allow all emissions and errors to come through as wrapped notifications then deal with them as you wish:
.flatMap(task -> {
try {
return new MyCommand(task.getTaskId(),groupName,task)
.toObservable()
.subscribeOn(this.schedulerFactory.get(groupName))
.materialize();
} catch(Exception ex) {
return Observable.error(ex).materialize();
}
});

CompletableFuture swallows exceptions?

I've been playing around with CompletableFuture and noticed a strange thing.
String url = "http://google.com";
CompletableFuture<String> contentsCF = readPageCF(url);
CompletableFuture<List<String>> linksCF = contentsCF.thenApply(_4_CompletableFutures::getLinks);
linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
linksCF.get();
If, in my thenAccept call, the assertion fails, the exception is not propagated.
I tried something even uglier then:
linksCF.thenAccept(list -> {
String a = null;
System.out.println(a.toString());
});
nothing happens, no exception is propagated. I tried using methods like handle and others related to exceptions in CompletableFutures, but failed - none is propagating the exception as expected.
When I debugged the CompletableFuture, it does catch the exception like this:
final void internalComplete(T v, Throwable ex) {
if (result == null)
UNSAFE.compareAndSwapObject
(this, RESULT, null,
(ex == null) ? (v == null) ? NIL : v :
new AltResult((ex instanceof CompletionException) ? ex :
new CompletionException(ex)));
postComplete(); // help out even if not triggered
}
and nothing else.
I'm on JDK 1.8.0_05 x64, Windows 7.
Am I missing something here?
The problem is you never request to receive the results of your call to linksCF.thenAccept(..).
Your call to linksCF.get() will wait for the results of the execution in your chain. But it will only return the results of then linksCF future. This doesn't include the results of your assertion.
linksCF.thenAccept(..) will return a new CompletableFuture instance. To get the exception thrown call get() or check the exception status with isCompletedExceptionally() on the newly return CompletableFuture instance.
CompletableFuture<Void> acceptedCF = linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
acceptedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return null;
});
acceptedCF.get(); // will throw ExecutionException once results are available
Alternative?
CompletableFuture<List<String>> appliedCF = linksCF.thenApply(list -> {
assertThat(list, not(empty()));
return list;
});
appliedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return Coolections.emptyList();
});
appliedCF.get(); // will throw ExecutionException once results are available
Although the question is basically already answered by Gregor Koukkoullis (+1), here is a MCVE that I created to test this.
There are several options for obtaining the actual exception that caused the problem internally. However, I don't see why calling get on the future that is returned by thenAccept should be an issue. In doubt, you could also use thenApply with the identity function and use a nice fluent pattern, like in
List<String> list =
readPage().
thenApply(CompletableFutureTest::getLinks).
thenApply(t -> {
// check assertion here
return t;
}).get();
But maybe there's a particular reason why you want to avoid this.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class CompletableFutureTest
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> contentsCF = readPage();
CompletableFuture<List<String>> linksCF =
contentsCF.thenApply(CompletableFutureTest::getLinks);
CompletableFuture<Void> completionStage = linksCF.thenAccept(list ->
{
String a = null;
System.out.println(a.toString());
});
// This will NOT cause an exception to be thrown, because
// the part that was passed to "thenAccept" will NOT be
// evaluated (it will be executed, but the exception will
// not show up)
List<String> result = linksCF.get();
System.out.println("Got "+result);
// This will cause the exception to be thrown and
// wrapped into an ExecutionException. The cause
// of this ExecutionException can be obtained:
try
{
completionStage.get();
}
catch (ExecutionException e)
{
System.out.println("Caught "+e);
Throwable cause = e.getCause();
System.out.println("cause: "+cause);
}
// Alternatively, the exception may be handled by
// the future directly:
completionStage.exceptionally(e ->
{
System.out.println("Future exceptionally finished: "+e);
return null;
});
try
{
completionStage.get();
}
catch (Throwable t)
{
System.out.println("Already handled by the future "+t);
}
}
private static List<String> getLinks(String s)
{
System.out.println("Getting links...");
List<String> links = new ArrayList<String>();
for (int i=0; i<10; i++)
{
links.add("link"+i);
}
dummySleep(1000);
return links;
}
private static CompletableFuture<String> readPage()
{
return CompletableFuture.supplyAsync(new Supplier<String>()
{
#Override
public String get()
{
System.out.println("Getting page...");
dummySleep(1000);
return "page";
}
});
}
private static void dummySleep(int ms)
{
try
{
Thread.sleep(ms);
}
catch (InterruptedException e)
{
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
If, in my thenAccept call, the assertion fails, the exception is not propagated.
The continuation that you register with thenAccept() is a separate task from the linksCF future. The linksCF task completed successfully; there is no error for it to report. It has its final value. An exception thrown by linksCF should only indicate a problem producing the result of linksCF; if some other piece of code that consumes the result throws, that does not indicate a failure to produce the result.
To observe an exception that happens in a continuation, you must observe the CompletableFuture of the continuation.
correct. but 1) I should not be forced to call get() - one of the points of the new constructs; 2) it's wrapped in an ExecutionException
What if you wanted to hand the result off to multiple, independent continuations using thenAccept()? If one of those continuations were to throw, why should that impact the parent, or the other continuations?
If you want to treat linksCF as a node in a chain and observe the result (and any exceptions) that happen within the chain, then you should call get() on the last link in the chain.
You can avoid the checked ExecutionException by using join() instead of get(), which will wrap the error in an unchecked CompletionException (but it is still wrapped).
The answers here helped me to manage exception in CompletableFuture, using "exceptionnaly" method, but it missed a basic example, so here is one, inspired from Marco13 answer:
/**
* Make a future launch an exception in the accept.
*
* This will simulate:
* - a readPage service called asynchronously that return a String after 1 second
* - a call to that service that uses the result then throw (eventually) an exception, to be processed by the exceptionnaly method.
*
*/
public class CompletableFutureTest2
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> future = readPage();
CompletableFuture<Void> future2 = future.thenAccept(page->{
System.out.println(page);
throw new IllegalArgumentException("unexpected exception");
});
future2.exceptionally(e->{
e.printStackTrace(System.err);
return null;
});
}
private static CompletableFuture<String> readPage()
{
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
// FUTURE: normal process
future.complete("page");
}).start();
return future;
}
}
The mistake to avoid is to call "exceptionnaly" on the 1st future (the variable future in my code) instead of the future returned by the "thenAccept" which contains the lambda that may throw an exception (the variable future2 in my code).
.
As usual, understanding the behavior of CompletableFuture is better left to the official docs and a blog.
Each then...() chaining method of the CompletableFuture class, which implements CompletionStage, accepts a an argument a CompletionStage. The stage that is passed depends on which order of then...() methods you've chained. Again, docs, but here's that aforementioned blog.

Categories