How about using @Async?

Marcos
Javarevisited
Published in
4 min readJul 12, 2022

--

An annotation that helps to build asynchronous execution with Spring Boot and Java

First of all the official documentation is right here https://spring.io/guides/gs/async-method/

In this example, I used Spring Boot 2.7.1 and Java 11.

Sometimes we can do some action inside our programs and may not wait until the end of the process. In this case, you can use the annotation @Async.

When you put @Async over a method that means this method will execute in a separate thread and the caller will not wait for the completion of the method called.

Show me the code

How we can enable this?

This way:

@EnableAsync
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

The @EnableAsyn shows Spring that some @Async annotated methods exist. You may create a configuration class if you want.

And now we can write this code:

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class ExampleService {

@Async
public void execute() {
try {
Thread.sleep(10000);
System.out.println(LocalDateTime.now() + " | test finished");
} catch (Exception e) {
e.printStackTrace();
}
}

}

And we call this method here:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

private final ExampleService exampleService;

public TestController(ExampleService exampleService) {
this.exampleService = exampleService;
}

@GetMapping("test-async")
public String test(){
exampleService.execute();
System.out.println("executing final of request before the method execute()");
return "ok";
}

}

Look at the result in a GET request on http://localhost:8080/async-test

“executing final of the request before the method execute” shows us that the controller method didn’t wait until the finish of the controller method.

An interesting use case:

  • send emails during a request without waiting for the process to be terminated

Going deep

AsyncResult class

In my example, I made a void method, but If you must handle some results you can use the AsyncResult class and do something with the return.

I will modify this code and log in controller class the return of method execute().

import java.time.LocalDateTime;
import java.util.concurrent.Future;

@Service
public class ExampleService {

@Async
public Future<String> execute() {
try {
Thread.sleep(10000);
var response = new AsyncResult<String>(LocalDateTime.now() + " | test finished");
System.out.println(response.get());
return response;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

}

AsyncResult implements the interface Future and the diamond can be any object.

The controller class:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
public class TestController {

private final ExampleService exampleService;

public TestController(ExampleService exampleService) {
this.exampleService = exampleService;
}

@GetMapping("test-async")
public String test(){
try {
var response = exampleService.execute();
if(response.isDone()){
System.out.println("method execute finished");
} else {
System.out.println(LocalDateTime.now() + " method execute not finished");
}
return "ok";
} catch (Exception e) {
return "nok";
}
}

}

And the result:

The response was sent before the method execute() has finished.

Handle with exceptions

First I wrote a class that implements AsyncUncaughtExceptionHandler:

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import java.lang.reflect.Method;


public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
System.out.println("Message from exception - " + throwable.getMessage());
System.out.println("Method name " + method.getName());
}

}

Second I created a configuration class and moved the annotation @EnableAsync to this class, this way:

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}

}

Note that, here I register the AsyncExceptionHandler.

Then I made another method with a purposeful exception:

@Async
public void executeWithException() throws Exception{
Thread.sleep(10000);
throw new Exception("Some exception");
}

And finally the controller:

@GetMapping("test-async2")
public String test2() throws Exception {
exampleService.executeWithException();
return "ok";
}

The result was:

The code is here: https://github.com/mmarcosab/async-example

--

--

Marcos
Javarevisited

I study software development and I love memes.