2

There is a quite similar question here, but the answer does not suffice for my question.

I have this method in a @Service class:

@Async
public void activateUser(...){
  if(someCondition){
   throw new GeneralSecurityException();
  }
}

The controller:

@GetMapping( "/activate")
public ResponseEntity<Void> activate(...){
    myService.activateUser(...);
}

And the controller advice:

@RestControllerAdvice( basePackages = "com.app.security" )
public class SecurityAdviceController extends ResponseEntityExceptionHandler {

     @ExceptionHandler( GeneralSecurityException.class )
     public ResponseEntity<GeneralSecurityExceptionBody> handleGeneralSecurityException( GeneralSecurityException ex ) {
     return ResponseEntity
            .status( HttpStatus.MOVED_PERMANENTLY )
            .header( HttpHeaders.LOCATION, ex.getUrl() )
            .body( null );
}

Here we are. Since the exception will be thrown in another thread, how can I proceed to make it available for @RestControllerAdvice?

Many suggest to implement AsyncUncaughtExceptionHandler, and I agree, but that does not answer the question.

When I remove @Async, everything is fine, and I can see that the same thread does all the tasks, but with @Async I have 2 threads involved.

One solution would be to get the exception thrown by the parent thread (but it's too cumbersome, and I don't see how to implement that).

Thanks for your help.

akuma8
  • 4,160
  • 5
  • 46
  • 82
  • I could be off or not understanding the full scenario here, but I'm not clear how this would work. When the request comes in, the Async method is gonna get kicked off on another thread and the request lifecycle is going to end?? How do you expect the server to then say, "oh wait caller, I forgot to mention something happened on a previous request, here's your error message!" ?? – Hermann Steidel Feb 24 '19 at 02:12
  • Yes you got it. We are in 21th century and this scenario is absolutely possible. Take a look at `DeferredResult` https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/request/async/DeferredResult.html – akuma8 Feb 24 '19 at 14:47
  • Okay there, buddy. I get that we have asynchronous HTTP frameworks but nothing in both your question nor the one you referenced suggest that you are using it. Which is why I qualified that "I may not have all the info" and assumed you are using plain old synchronous Spring Web. I agree with @Zgurski, if you aren't using the right tool, then I'm not sure you are going to get the answer you want. Square peg, round hole. – Hermann Steidel Feb 24 '19 at 18:15
  • My comment was for this remark: "How do you expect the server to then say, "oh wait caller, I forgot to mention something happened on a previous request, here's your error message!" ??". I don't think using Spring Webflux for a such simple case is the solution. It's like using a chain saw to cut a flower. – akuma8 Feb 25 '19 at 09:48
  • Understandable, all good. – Hermann Steidel Feb 25 '19 at 12:57

1 Answers1

1

If you really want to work asynchronous, then most likely you are using wrong tools - better to switch to Spring WebFlux and use reactive approach instead.

Going back to the question, I can suggest 2 approaches:

  • Get rid of @Async or use SyncTaskExecutor, so the task will be executed synchronously in the calling thread.
  • Get rid of @ExceptionHandler(GeneralSecurityException.class) for this particular method. Instead, use CompletableFuture and provide exceptionally handled logic. Below is a sketch of using CompletableFuture in controller and service:
@Controller
public class ApiController {
    private final Service service;
    public ApiController(Service service) {
        this.service = service;
    }
    @GetMapping( "/activate")
    public CompletableFuture<Void> activate(...){
        return service.activateUser(...)
               .exceptionally(throwable -> ... exception handling goes here ...)
    }
}

@Service
public class Service {
    @Async
    public CompletableFuture<Void> activateUser(...) {
        CompletableFuture<Void> future = new CompletableFuture<>();
        ... your code goes here ...
        if(someCondition){
           future.completeExceptionally(new GeneralSecurityException());
        } else {
           future.complete(null);
        }
        return future;
    }
}

Oleksii Zghurskyi
  • 3,967
  • 1
  • 16
  • 17
  • Why are you talking about testing? This is not my question – akuma8 Feb 24 '19 at 15:00
  • @akuma8 Sorry for initial response - it really was off-topic. I've updated the answer. – Oleksii Zghurskyi Feb 24 '19 at 16:25
  • I'll give a try to the 2nd case. I can't rid of `@ExceptionHandler(GeneralSecurityException.class)` because I would like to separate exception handling from other logic. I'll let you know. – akuma8 Feb 25 '19 at 09:42