0

I have a requirement. I have 2 processes

  1. Contact creation and
  2. Associating contact to the Department

Currently I have a spring boot API which has a REST POST call to perform both in one thread. Since process 2 is taking more time I wanted to run that in the background immediately after finishing the step 1.

@PostMapping(value = "/processDeptContact", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PayloadResponse> processDeptContact(@RequestBody String payload) {

    ResponseEntity response = new ResponseEntity(new ErrorResponse("Exception"),
            new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    try {
        response = myService.processPayload(payload);

    } catch (Exception e) {
        logger.error("Exception in the controller");
    }
    return response;
}

I want to return the response to the user as soon as step 1 is done and performing step 2 at the background. How do I achieve that

Thanks in advance

user3919727
  • 283
  • 2
  • 7
  • 25

4 Answers4

1

In your main class, or a @Configuration class, use @EnableAsync to bootstrap a thread pool:

@EnableAsync
@SpringBootApplication
public class DemoApplication {

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

You can optionally set Thread Pool properties under spring.task.execution.pool property. Example:

spring:
  task:
    execution:
      pool:
        core-size: 8
        max-size 16

Here's a stack post detailing what each property means: Core pool size vs maximum pool size in ThreadPoolExecutor

Inside your controller:

@RestController
public class TestController {

    private final ContactService contactService;
    private final DepartmentService departmentService;

    // Constructor Injection
    public TestController(ContactService contactService, DepartmentService departmentService) {
        this.contactService = contactService;
        this.departmentService = departmentService;
    }

    @PostMapping(value = "/processDeptContact", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<PayloadResponse> processDeptContact(@RequestBody String payload) {
    
        List<Contact> contacts = contactService.processPayload(payload);
        departmentService.associateContacts(contacts);   // This is an asynchronous call
        return ResponseEntity.ok(new PayloadResponse(contacts));
    }

}

I've removed the try/catch from the controller method since error handling is a cross cutting concern and is handled by AOP. More on that here: Baeldung

And finally in your DepartmentService, you use the @Async annotation to turn it into an asynchronous method:

@Service
public class DepartmentService {

    @Async
    public void associateContacts(List<Contact> contacts) {
        // method
    }
}

I see other answers are basically saying the same thing and are correct, but not complete so I felt the need to put everything together for you.

George
  • 2,820
  • 4
  • 29
  • 56
  • Thanks for the solution. One thing I noticed is -calling the async method from within the same class won't work, that's the limitation of @Async - this might help somebody if they have stuck in this – user3919727 Sep 24 '20 at 18:17
  • This is the limitation of AOP. You need to understand how Spring handles AOP and proxy classes. – George Sep 24 '20 at 18:18
  • @user3919727 Essentially, internal method calls are calling your real object, but it the Async AOP is implemented in the proxy class instantiated by Spring Boot. – George Sep 24 '20 at 18:20
0

Spring framework provides support for asynchronous processing out of the box. Spring can create & manage threads for us by providing support for various TaskExecutor abstraction.

We can create a method in a new class that will do the second process (associate contact to the Department) and annotate that method with @Aysnc. The annotation ensures the spring executes this method as a Runnable/Future depending on return type.

Sample Implementation (We have to add @EnableAsync in any of our configuration class)

@Component
class ContactManager {
   
   @Async
   public void associateContactToDepartment(){
       //method implementation goes here
   }
}

class MyService {
   
    @Autowired
    private ContactManager contactManager;
    
    public PayloadResponse processPayload(String payload){
        payloadResponse payloadResponse = createContact();//first process
        contactManager.associateContactToDepartment() // this call will be executed asynchronously.
        return payloadResponse;
    }
}

Refer this for quick intro to async methods.

Kumar V
  • 1,570
  • 1
  • 12
  • 19
0

Follow the below steps:

  1. Add @EnableAsync annotation and Add TaskExecutor Bean to main spring boot application class

     @SpringBootApplication
     @EnableAsync
     public class AsynchronousSpringBootApplication {
    
         private static final Logger logger = LoggerFactory.getLogger(SpringBootApplication.class);
    
         @Bean(name="processExecutor")
         public TaskExecutor workExecutor() {
             ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
             threadPoolTaskExecutor.setThreadNamePrefix("Async-");
             threadPoolTaskExecutor.setCorePoolSize(3);
             threadPoolTaskExecutor.setMaxPoolSize(3);
             threadPoolTaskExecutor.setQueueCapacity(600);
             threadPoolTaskExecutor.afterPropertiesSet();
             logger.info("ThreadPoolTaskExecutor set");
             return threadPoolTaskExecutor;
         }
    
         public static void main(String[] args) throws Exception {
       SpringApplication.run(SpringBootApplication.class,args);
      }
    
  1. Add the contact to department method as below:

         @Service
         public class DepartmentProcess {
    
             private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);
    
             @Async("processExecutor")
             @Override
             public void processDepartment() {
                 logger.info("Received request to process in DepartmentProcess.processDepartment()");
                 try {
                     Thread.sleep(15 * 1000);
                     logger.info("Processing complete");
                 }
                 catch (InterruptedException ie) {
                     logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
                 }
             }
         }
    
  2. Call the method from the controller as below:

     @PostMapping(value = "/processDeptContact", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<PayloadResponse> processDeptContact(@RequestBody String payload) {
    
    ResponseEntity response = new ResponseEntity(new ErrorResponse("Exception"),
             new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
     try {
         response = myService.processPayload(payload);
          myService.processDepartment();//async method
     } catch (Exception e) {
         logger.error("Exception in the controller");
     }
     return response;
    

    }

Umesh Sanwal
  • 681
  • 4
  • 13
  • Spring boot already gives you a `TaskExecutor` bean you don't need to construct a new one. – George Sep 24 '20 at 03:18
  • Yes this is true . If we do not define an Executor bean, Spring creates a SimpleAsyncTaskExecutor and uses that. – Umesh Sanwal Sep 24 '20 at 03:22
  • By default, it uses `ThreadPoolTaskExecutor`, not `SimpleAsyncTaskExecutor`. [Source](https://github.com/spring-projects/spring-boot/blob/c44e1ec0ad8d910a02aaa70b1c1820bb27a5526b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java#L279) – George Sep 24 '20 at 03:56
-1

Points 1 and 2 are not here but it doesn't matter, let's call them foo1() and foo2().

In myService.processPayload() you want to do:

    ResponseEntity result = foo1();
    Runnable runnable = () -> {
      foo2()
    };
    Thread thread = new Thread(runnable);
    thread.start(); // the logic in foo2 will happen in a background thread so it will not block on this line, consider using a thread pool instead
    return result;

BTW, this sounds like premature optimization and you should think about race conditions with parallel threads but this is not what the question was asking.

One more thing, move this to the catch because it's a waste of instantiations if the try will succeed, which should happen most of the time.

ResponseEntity response = new ResponseEntity(new ErrorResponse("Exception"),
            new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
shinzou
  • 5,850
  • 10
  • 60
  • 124