So I'm trying to create a CustomCompletableFuture and override the get, I don't have to handle all the exceptions every time I use it, my code looks like this:
The custom completable is like this:
public class CustomCompletableFuture<T> extends CompletableFuture<T> {
@Override
public T get(long timeOut, TimeUnit unit) {
try {
return super.get();
} catch (InterruptedException | ExecutionException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
if (e.getCause() instanceof ServiceSystemException) {
throw (ServiceSystemException) e.getCause();
} else if (e.getCause() instanceof ServiceBusinessException) {
throw (ServiceBusinessException) e.getCause();
}
throw ExceptionBuilder.buildServiceSystemException(EXTERNAL_SERVICE_EXCEPTION,
ServiceExceptionCodeMap.getErrorDescription(EXTERNAL_SERVICE_EXCEPTION),
ServiceExceptionCodeMap.getErrorMessageFormat(EXTERNAL_SERVICE_EXCEPTION));
}
}
}
And here is the usage:
CustomCompletableFuture<List<Student>> future1Student = getStudentsByProperty(propertyId, propertyCode);
List<Student> students = future1Student.get(futuresExecutorConfigurationProperties.getRatePlanSeasonTimeout(), TimeUnit.MILLISECONDS);
private CustomCompletableFuture<List<Student>> getStudentsByProperty(String propertyId, String propertyCode) {
Map<String, String> parentMDCContext = MDC.getCopyOfContextMap();
return (CustomCompletableFuture<List<Student>>) CustomCompletableFuture.supplyAsync(() -> {
try {
MDC.setContextMap(parentMDCContext);
return internalService.internalCall();
} finally {
MDC.clear();
}
}, getSeasonExecutor);
}
The CompletableFuture#supplyAsync(Supplier)
is a static method. When you do:
CustomCompletableFuture.supplyAsync(...);
You're still calling the method declared in CompletableFuture
, meaning the method is not returning a CustomCompletableFuture
. Note static methods cannot be overridden. Though if you were to define a static method with the same signature in the subclass, then it would "hide" the static method in the superclass. That is not likely something you want to do.
That said, I recommend you do not subclass CompletableFuture
this way. By unwrapping the cause of the ExecutionException
and throwing it directly, you are breaking the contract of get
. If you need this exception handling in multiple places, I suggest creating a utility method.
public static <T> T getServiceResult(Future<T> future, Duration timeout) throws TimeoutException {
TimeUnit unit = TimeUnit.NANOSECONDS;
try {
return future.get(unit.convert(timeout), unit);
} catch (InterruptedException | ExecutionException ex) {
if (ex instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
// "JEP 394: Pattern Matching for instanceof" added in Java 16
if (ex.getCause() instanceof ServiceSystemException sse) {
throw sse;
} else if (ex.getCause() instanceof ServiceBusinessException sbe) {
throw sbe;
}
throw ExceptionBuilder.buildServiceSystemException(
EXTERNAL_SERVICE_EXCEPTION,
ServiceExceptionCodeMap.getErrorDescription(EXTERNAL_SERVICE_EXCEPTION),
ServiceExceptionCodeMap.getErrorMessageFormat(EXTERNAL_SERVICE_EXCEPTION));
}
}
Then you can call this like so:
CompletableFuture<List<Student>> future = getStudentsByProperty(propertyId, propertyCode);
long timeoutMillis = futuresExecutorConfigurationProperties.getRatePlanSeasonTimeout();
List<Student> students = getServiceResult(future, Duration.ofMillis(timeoutMillis));
Where getStudentsByProperty
returns a standard CompletableFuture
.