I'm trying to make a small REST using Spring Boot. I've never used Spring and used Java a long time ago (Java 7)!
In the last 2 years I have used only Python and C# (but like I said, I already used Java).
So, now, I'm trying to make a REST using async methods, and checked several examples, but still, I don't understand very well the "correct way" to do this.
Looking at the following documentation: http://carlmartensen.com/completablefuture-deferredresult-async, Java 8 has CompletableFuture
that I can use with Spring, so, I made the following code:
Service:
@Service
public class UserService {
private UserRepository userRepository;
// dependency injection
// don't need Autowire here
// https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Async
public CompletableFuture<User> findByEmail(String email) throws InterrupedException {
User user = userRepository.findByEmail(email);
return CompletableFuture.completedFuture(user);
}
}
Repository:
public interface UserRepository extends MongoRepository<User, String> {
@Async
findByEmail(String email);
}
RestController:
@RestController
public class TestController {
private UserService userService;
public TestController(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = "test")
public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
return userService.findByEmail(email).thenApplyAsync(user -> {
return user;
})
}
}
This code give me the expected output. Then, looking at another documentation (sorry, I lost the link), I see that Spring accept the following code (which give me the expected output too):
@RequestMapping(value = "test")
public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
return userService.findByEmail(email);
}
}
Is there a difference between the two methods?
Then, looking at the following guide: https://spring.io/guides/gs/async-method/, there's a @EnableAsync
annotation in SpringBootApplication
class.
If I include the @EnableAsync
annotation and create a asyncExecutor
Bean like the code from last link, my application don't return nothing on /test
endpoint (only a 200 OK response, but with blank body).
So, my rest is async without the @EnableAsync
annotation?
And why when I use @EnableAsync
, the response body is blank?
The response body is blank because the
@Async
annotation is used at findEmail method of UserRepository class, it means that there is no data returned to the following sentenceUser user = userRepository.findByEmail(email);
because findByEmail method is running on other different thread and will return null instead of a List object.The
@Async
annotation is enabled when you declare@EnableAsync
that is the reason why it only happens when you use@EnableAsync
because it activates the @Async of findEmail method to run it on other thread.The method
return userService.findByEmail(email);
will return aCompletableFuture
object that is created fromUserService
class.The difference with the second method call is that
thenApplyAsync
method will create a totally newCompletableFuture
from the previous one that comes fromuserService.findByEmail(email)
and will only return the user object that comes from the firstCompletableFuture
.If you want to get the expected results just remove the
@Async
annotation from findByEmail method, and finally add the@EnableAsync
AnnotationIf you need to clarify ideas of how to use Async methods, lets say that you have to call three methods and each one takes 2 seconds to finish, in a normal scenario you will call them method1, then method2 and finally method3 in that case you entire request will take 6 seconds. When you activate the Async approach then you can call three of them and just wait for 2 seconds instead of 6.
Add this long method to user service:
And call it three times from Controller, like this
Finally measure the time that takes your response, if it takes more than 6 seconds then you are not running Async method, if it takes only 2 seconds then you succeed.
Also see the following documentation: @Async Annotation, Spring async methods, CompletableFuture class
Hope it help.
I'm facing performance issues when triggering Async methods. The Asynchronous child threads start executing very late (around 20 to 30 seconds delay). I'm using ThreadPoolTaskExecutor() in my main SpringBoot application class. You can also try the same if you consider perfomance as a factor.