Your attempt is going in the right direction, but not right. Your getResultClassD() method returns an already created object of type ClassD , onto which an arbitrary stream will call modification methods without notifying the caller of getResultClassD() . This can lead to race conditions if the modification methods themselves are not thread safe, otherwise the caller will not know when the ClassD instance ClassD really ready for use.
Correct solution:
public CompletableFuture<ClassD> getResultClassD() { CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() ); CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() ); CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() ); return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) .thenApplyAsync(dummy -> { ClassD resultClass = new ClassD(); ClassA classA = classAFuture.join(); if (classA != null) { resultClass.setClassA(classA); } ClassB classB = classBFuture.join(); if (classB != null) { resultClass.setClassB(classB); } ClassC classC = classCFuture.join(); if (classC != null) { resultClass.setClassC(classC); } return resultClass; }); }
Now the calling getResultClassD() object can use the returned CompletableFuture to query the execution status or chain-dependent actions, or use join() to get the result after the operation completes.
To solve other issues, yes, this operation is asynchronous and using join() within lambda expressions is appropriate. join was precisely created because Future.get() declared to throw checked exceptions makes unnecessary use in these lambda expressions.
Note that null tests are useful if these service.getClassX() can actually return null . If one of the service calls fails with an exception, the entire operation (represented by CompletableFuture<ClassD> ) will complete exclusively.
source share