Search code examples
javaspringsubclasscompletable-future

CompletableFuture cannot be cast to CustomCompletableFuture


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);
        }

Solution

  • 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.