0

I started working with CompletableFuture in Spring Boot, and I'm seeing in some places that the usual repository methods return CompletableFuture <Entity> instead of Entity.

I do not know what is happening, but when I return instances of CompletableFuture in repositories, the code runs perfectly. However when I return entities, the code does not work asynchronously and always returns null.

Here is an example:

@Service
public class AsyncServiceImpl{
    /** .. Init repository instances .. **/

    @Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
    public CompletableFuture<Token> getTokenByUser(Credential credential) {
        return userRepository.getUser(credential)
            .thenCompose(s -> TokenRepository.getToken(s));
    }
}

@Repository
public class UserRepository {

    @Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
    public CompletableFuture<User> getUser(Credential credentials) {
        return CompletableFuture.supplyAsync(() -> 
            new User(credentials.getUsername())
        );
    }       
}

@Repository
public class TokenRepository {

    @Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
    public CompletableFuture<Token> getToken(User user) {
        return CompletableFuture.supplyAsync(() -> 
            new Token(user.getUserId())
        );
    }
}

The previous code runs perfectly but the following code doesn't run asynchronously and the result is always null.

@Service
public class AsyncServiceImpl {
    /** .. Init repository instances .. **/

    @Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
    public CompletableFuture<Token> requestToken(Credential credential) {
        return CompletableFuture.supplyAsync(() -> userRepository.getUser(credential))
            .thenCompose(s -> 
                CompletableFuture.supplyAsync(() -> TokenRepository.getToken(s)));
    }
}

@Repository
public class UserRepository {
    @Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
    public User getUser(Credential credentials) {
        return new User(credentials.getUsername());
    }       
}

@Repository
public class TokenRepository {
    @Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
    public Token getToken(User user) {
        return new Token(user.getUserId());
    }
}

Why doesn't this second code work?

FMCR
  • 87
  • 6

1 Answers1

2

As per the Spring @Async Javadoc:

the return type is constrained to either void or Future

and it is also further detailed in the reference documentation:

In the simplest case, the annotation may be applied to a void-returning method.

[…]

Even methods that return a value can be invoked asynchronously. However, such methods are required to have a Future typed return value. This still provides the benefit of asynchronous execution so that the caller can perform other tasks prior to calling get() on that Future.

In your second example, your @Async-annotated methods do not return a Future (or ListenableFuture and CompletableFuture which are also supported). However, Spring has to run your method asynchronously. It can thus only behave as if your method had a void return type, and thus it returns null.

As a side note, when you use @Async, your method will already run asynchronously, so you shouldn't use CompletableFuture.supplyAsync() inside the method. You should simply compute your result and return it, wrapped in CompletableFuture.completedFuture() if necessary. If your method is only composing futures (like your service that simply composes asynchronous repository results), then you probably don't need the @Async annotation. See also the example from the Getting Started guide.

Community
  • 1
  • 1
Didier L
  • 18,905
  • 10
  • 61
  • 103