Reputation: 4691
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.
Upvotes: 2
Views: 1876
Reputation: 4365
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:
@Async
or use SyncTaskExecutor
, so the task will
be executed synchronously in the calling thread.@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;
}
}
Upvotes: 1