package net.andreaskluth; import java.util.concurrent.CompletableFuture; import org.springframework.core.MethodParameter; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; public class CompletableFutureReturnValueHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { return CompletableFuture.class.isAssignableFrom(returnType.getParameterType()); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { mavContainer.setRequestHandled(true); return; } final DeferredResult<Object> deferredResult = new DeferredResult<>(); @SuppressWarnings("unchecked") CompletableFuture<? extends Object> futureValue = (CompletableFuture<? extends Object>) returnValue; futureValue.whenComplete((result, ex) -> { if (ex != null) { deferredResult.setErrorResult(ex); } else { deferredResult.setResult(result); } }); startDeferredResultProcessing(mavContainer, webRequest, deferredResult); } /** * Added to enable partial mocking and unit testing. */ void startDeferredResultProcessing(ModelAndViewContainer mavContainer, NativeWebRequest webRequest, final DeferredResult<Object> deferredResult) throws Exception { WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer); } }