Handle exception from service classes in Spring-Boot
Introduction
While developing spring-boot apps I have faced challenge of handling exception from service methods. Any exception that are propogated from the Controllers
are handled by @ControllerAdvice
. AFAIK there is nothing called as @ServiceAdvice
to handle any exception being thrown from service layer.
Spring tool has couple of solutions:
- AOP
- Dynamic Code generation (ByteBuddy)
I always tried to do a workaround for this by creating an @AroundAspect
and then handling ladder of exception from the aspect class.
Example
First lets define those failing services and their call from the controller.
Service - This is a service class which randomly throws the exception from the service.
1@Service
2public class SuspiciousService {
3 final Random random = new Random();
4
5 public String beSuspicious() {
6 final boolean value = random.nextBoolean();
7 if (value) {
8 throw new IllegalStateException("Exception occured for: " + value);
9 }
10 return "I am not suspicious";
11 }
12}
Controller - Calls the service interface and return the result back to the caller
1@RestController
2@RequestMapping("/is-suspicious")
3@AllArgsConstructor
4public class SampleController {
5 private final SuspiciousService suspiciousService;
6
7 @GetMapping
8 public Map<String, String> get() {
9 return Map.of("isSuspicious", suspiciousService.beSuspicious());
10 }
11
12}
Under normal flow i'd usually catch IllegalStateException
at
- Service Layer - In this case i will have to write a try catch block in service
- Controller Layer - In this case i will have to write a try catch block in controller or if i plan to omit that then it will get caught in
@ControllerAdvice's
method declaration and most probably i will have to return an response somewhere near error only instead of success.
When i use @AroundAspect
i have to handle the condition more gracefully and return response. My Aspect
would be more then just trying to handle try catch block and do not want the application to go to Throwable path and then to ControllerAdvice
.
Here is how AroundAspect
looks like
1@Aspect
2@Component
3@Order(2)
4public class AspectAroundSuspiciousService {
5
6 @Around("execution(* fully.qualified.classname.beSuspicious(..))")
7 public Object parallelExecuteBeforeAndAfterCompose(ProceedingJoinPoint point) throws Throwable {
8 try {
9 return point.proceed();
10 } catch (RuntimeException re) {
11 return "Yes, I am suspicious";
12 }
13 }
14
15}
Now, next time when the exception is thrown is try to visualize that line
1 return Map.of("isSuspicious", suspiciousService.beSuspicious());
is replaced with
1 return Map.of("isSuspicious", "Yes, I am suspicious");
This approach is very handy but can also hamper code-quality if there is a lot of cognitive-complexity present in the aspect method. I used this mostly in cases of database exceptions and rest-api for service classes.
comments powered by DisqusThere is no harm in throwing exceptions, it should not go unnoticed. Eat them only when you are hungry 😋