Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Async in SpringBoot creating new thread but Controller waits for async call to finish before sending a response back

Context

I've a scenario - I need to expose a rest endpoint and provide a post method , which will be used by a fronend to receive form values (name, email, address). With these details I need ti call a third party api (that could take upto 10 seconds to respond). Additionally , I need to store the processed application and the response in DB somewhere to keep a track for monitoring purposes.

Problem I plan to use Spring @Async functionality for storing details in Queue (the a DB) so that I dont keep the user waiting for response while I do this storing. The @Async seems to be creating new Thread , I can see from the logs but Controller doesn’t send the response back to client (Which is contrary to actual @Async knowledge I have which is not alot) ; So, until the async completes user has to wait for the response.

Is there anything wrong here or missing ?

TIA for your help.

here are some snippets of my classes-

Main

@EnableAsync(proxyTargetClass = true)
@EnableScheduling
@SpringBootApplication
public class CardsApplication {

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

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }

}

Controller

@Validated
@RequiredArgsConstructor
public class CardEligibilityController {
    private final CardEligibilityService cardEligibilityService;

    @PostMapping("/check-eligibility")
    @CrossOrigin(origins = "*")
    public EligibilityResponse checkEligibility(@RequestBody @Valid Applicant applicant){
        return cardEligibilityService.eligibilityService(applicant);
    }
}

Service 1

public interface CardEligibilityService {
    EligibilityResponse eligibilityService(Applicant applicant);
}

@Slf4j
@Service
@RequiredArgsConstructor
public class CardEligibilityServiceImpl implements CardEligibilityService {

    private final ThirdPartyEligibilityAdapter thirdPartyEligibilityAdapter;
    private final QueueService queueService;
    private final QueueMessageResponseService queueMessageResponseService;

    @Override
    public EligibilityResponse eligibilityService(Applicant applicant){
        EligibilityResponse eligibilityResponse = checkEligibility(applicant);
        queueService.pushMessage(queueMessageResponseService.createQueueResponse(applicant,eligibilityResponse));
        return eligibilityResponse;
    }

    private EligibilityResponse checkEligibility(Applicant applicant) {
        return thirdPartyEligibilityAdapter.getEligibility(applicant);
    }

}

Service 2

public interface QueueService {
     void pushMessage(QueueMessage queueMessage);
     void retry();
}


@Service
@RequiredArgsConstructor
@Slf4j
public class QueueServiceImpl implements QueueService{

    private final List<QueueMessage> deadQueue = new LinkedList<>();


    //TODO check why async gets response stuck
    @Override
    @Async
    public void pushMessage(QueueMessage queueMessage){
        try {
            //Push message to a queue - Queue settings Rabbit/Kafka - then this could be
            //used by listeners to persist the data into DB
            log.info("message queued {} ", queueMessage);
        } catch (Exception e) {
            log.error("Error {} , queueMessage {} ", e, queueMessage);
            deadQueue.add(queueMessage);
        }
    }

   
**This method is a fault tolerance mechanism in case push to queue had any issues, The Local Method call to pushMessage isn’t the problem I also tried this by deleting retry method method**

    @Override
    @Scheduled(fixedDelay = 300000)
    public void retry() {
        log.info("Retrying Message push if there are any failure in enqueueing ");
        final List<QueueMessage> temp = new LinkedList<>(deadQueue);
        deadQueue.clear();
        Collections.reverse(temp);
        temp.forEach(this::pushMessage);
    }

}

Service 3

public interface QueueMessageResponseService {
    QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse);
}

@Service
public class QueueMessageResponseServiceServiceImpl implements QueueMessageResponseService {
    @Override
    public QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse) {
        return new QueueMessage(applicant,eligibilityResponse);
    }
}

EDIT 2

THE MOST STRANGE BEHAVIOUR

If I add Thread.sleep(20); in my async method, This works as expected , the user gets a response back without waiting for async to complete. But Still unable to understand the cause.

@Async
    public void pushMessage(QueueMessage queueMessage) {
        try {
            //Push message to a queue - Queue settings Rabbit/Kafka - then this could be
            //used by listeners to persist the data into DB
            Thread.sleep(20);
            log.info("message queued {} ", queueMessage);
        } catch (Exception e) {
            log.error("Error {} , queueMessage {} ", e, queueMessage);
            deadQueue.add(queueMessage);
        }
    }
like image 624
Saurabh Sharma Avatar asked Sep 08 '25 07:09

Saurabh Sharma


1 Answers

The call to pushMessage in retry is a LOCAL call. So the proxy is not involved an the method is executed synchronously.

You have to move the async method to it's own class.

like image 83
Simon Martinelli Avatar answered Sep 10 '25 01:09

Simon Martinelli