Reputation: 2546
I have lots of scheduled tasks in my Spring Boot (ver 1.4.2) application and would like to catch all exceptions from them using one handler like is possible for normal controllers with @ExceptionHandler annotation. This approach does not work for tasks that are defined with @Scheduled annotation because of threading:
@Component
public class UpdateJob {
@Transactional
@Scheduled(cron = "0 1 0 * * *")
public void runUpdateUsers() {
userService.updateUsers();
}
@ExceptionHandler
public void handle(Exception e) {
// some more logic here
logger.error(e.getMessage());
}
}
@ExceptionHandler does not work for @Scheduled method (and it turns out it is not meant to). Instead, Spring Boot uses it's own LoggingErrorHandler:
2016-12-08 15:49:20.016 ERROR 23119 --- [pool-7-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
Can I somehow replace or supply default exception handler for scheduled tasks? Or would it make sense (and is it possible) to switch to PropagatingErrorHandler which as I understand propagates the error further? Is there any other way to achieve the goal using only Java configuration (no XML)?
This is not a duplicate of this question as it explicitly asks for a solution based on Java configuration, not XML (so it is decent to incorporate into Spring Boot project without any XML configuration).
There are also some answers that demonstrate how to configure TaskScheduler from the scratch. Eg this answer requires that you also define pool size, max pool size, queue capacity. Here is a solution which also need very extensive configuration. Documentation shows how to configure other aspects, but not how to specify error handling. But what is the minimal required effort with Java config so that I can maximally keep Spring Boot default values (thread pools, executor configurations etc).
Upvotes: 27
Views: 23370
Reputation: 11974
If you have XML configuration, this works:
<task:annotation-driven scheduler="customThreadPoolTaskScheduler" />
<bean id="customThreadPoolTaskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="10" />
<property name="errorHandler" ref="customScheduledTaskErrorHandler" />
</bean>
<bean id="customScheduledTaskErrorHandler" class="myapp.ScheduledJobErrorHandler"/>
<task:scheduled-tasks scheduler="customThreadPoolTaskScheduler">
<task:scheduled method="method1" cron="0 0 7 * * *"/>
<task:scheduled method="method2" cron="0 0 7 * * *"/>
etc.
Note that this replaces any <task:scheduler>
that you might have. You are now directly defining a ThreadPoolTaskScheduler
which allows you to specify an errorHandler
.
Implement the myapp.ScheduledJobErrorHandler
class with the required method:
public class ScheduledJobErrorHandler implements ErrorHandler {
@Override
public void handleError(Throwable t) {
//...
}
Upvotes: 0
Reputation: 191
You're on the right track with PropagatingErrorHandler
, but you slightly misunderstood the purpose. It causes the exception to continue further up the stack, causing the scheduled task to not run in the future. Also, it's private to TaskUtils
, so you can not access it.
You'll need to implement your own ErrorHandler
, but you can only have a single handler. At a minimum you'll want to log the exception, because you'll no longer have Spring logging it for you.
Assuming you don't have your own custom TaskScheduler
bean, to create your own ErrorHandler
in Spring Boot, implement org.springframework.boot.task.TaskSchedulerCustomizer
in your @Configuration class.
@Override
public void customize(ThreadPoolTaskScheduler taskScheduler) {
taskScheduler.setErrorHandler(new CustomErrorHandler());
}
private static class CustomErrorHandler implements ErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomErrorHandler.class);
@Override
public void handleError(Throwable t) {
logger.error("Scheduled task threw an exception: {}", t.getMessage(), t);
}
}
Because ErrorHandler
is a @FunctionalInterface
you can do:
taskScheduler.setErrorHandler(t -> { /* Handle exception here */});
(This is based on Spring Boot 2.1.8)
Upvotes: 8
Reputation: 131
I think using AOP might solve your problem.
Steps
FYI : I have worked on AOP on individual methods and caught its exceptions and logged it but personally I have not come up with the use case you have specified till now. The above solution is just a suggestion.
@Aspect
@Component
public class UpdateJobAOP {
@Pointcut("execution(* com.foo.bar.UpdateJob.*(..))")
public void all() {}
@AfterThrowing(pointcut="all()", throwing="ex")
public void afterThrowing(Exception ex) {
// Do what you want
ex.printStackTrace();
}
}
Upvotes: 3
Reputation: 3222
Here is an example of setting custom error handler (Spring 2.0.2):
@Bean
public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler();
scheduler.setErrorHandler(throwable -> { /* custom handler */ });
return scheduler;
}
Upvotes: 12
Reputation: 24561
As mentioned in comments, @ExceptionHandler
is for Spring MVC controllers.
In case you want to have exception handling logic for single scheduler, easiest and most maintainable would be to wrap it into try-catch block and handle error there.
If you want to apply same error handler for various schedulers, follow @M. Deinum's suggestion.
Upvotes: 3