javaspringspring-restcontrolleruncaughtexceptionhandler

How to Access a Request-Scoped Bean in a Spring Exception Handler?


I need to log trace information using a Tracer bean, which is request-scoped, in both my REST method and exception handler. The Tracer bean is consumed in the REST method to log the trace information at the start of processing the request, and I want to use the same trace message in the exception handler when an exception occurs.

However, I'm facing difficulties accessing the request-scoped Tracer bean in my exception handler. I have tried both rest controller @ExceptionHhandler annotation in the REST controller and @ControllerAdvice, but neither seams to support access to a scope bean.

Here's my setup:

@RestController
public class MyController {

    @Bean
    @RequestScope
    Tracer tracer(){
        return new Tracer();
    }
    @GetMapping(path = "list")
    public String list(Tracer trace){
        return  trace.getCorrelationId();
    }

    @GetMapping(path = "throw")
    public String throwEx(Tracer trace,HttpServletRequest req){
         throw new RuntimeException("Ops");
    }


    @ExceptionHandler({Exception.class})
    ResponseEntity<String> defaultExceptionHandler(Tracer tracer, HttpServletRequest req, Exception ex) {
    
        return ResponseEntity
                .internalServerError()
                .body("ERROR: " + tracer.getCorrelationId());

    }
}

The tracer (for completeness):

@Getter
public class Tracer {
    private final String requestId;
    private final String correlationId;


    public Tracer() {
        requestId = null;
        correlationId = UUID.randomUUID().toString();
    }
    public Tracer(String requestId, String correlationId) {
        this.requestId = requestId;
        this.correlationId = correlationId;
    }
}

Here the test:

@SpringBootTest
@AutoConfigureMockMvc
class MyControllerTest {
    @Autowired
    protected MockMvc mockMvc;

    @Test
    void testExceptionHandler() throws Exception {
  
        mockMvc
                .perform(MockMvcRequestBuilders.get("/throw")
                )
                .andDo(MockMvcResultHandlers.print())
                .andReturn()
                .getResponse()
                .getContentAsString();            
    }
}

And here is the problem:

Could not resolve parameter [0] in org.springframework.http.ResponseEntity<java.lang.String> com.baeldung.scopes.MyController.defaultExceptionHandler(com.baeldung.scopes.Tracer,jakarta.servlet.http.HttpServletRequest,java.lang.Exception): No suitable resolver

It seems that I cannot directly inject the Tracer bean into the exception handler method.

I'm I missing something here? Is there another approach to do this problem?

Any help or suggestions would be greatly appreciated!


Solution

  • UPDATE: Most correct way is this

    Maybe "dirty", but at least it works:

    @Getter
    public class YourException extends RuntimeException {
    
      private final Tracer tracer;
      
      //Your constructors
    }
    
    @GetMapping(path = "throw")
    public String throwEx(Tracer trace,HttpServletRequest req){
      throw new YourException("Ops", tracer);  -- pass your tracer to the constructor
    }
    
    
    @ExceptionHandler({YourException.class})
    ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, YourException ex) {
        
      return ResponseEntity
            .internalServerError()
            .body("ERROR: " + ex.getTracer().getCorrelationId());
    
    }
    

    Other solution is ThreadLocal (spring web-mvc uses thread-per-request model)

    public class TracerInfo {
      private static final ThreadLocal<Tracer> TRACER = new ThreadLocal<>();
    
      public static void set(Tracer tracer) {
        TRACER.set(tracer);
      }
    
      public static Tracer get() {
        return TRACER.get();
      }
    }
    
    @GetMapping(path = "throw")
    public String throwEx(Tracer trace,HttpServletRequest req) {
      TracerInfo.set(tracer);
      throw new YourException("Ops");
    }
    
    @ExceptionHandler({Exception.class})
    ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, Exception ex) {
        
      return ResponseEntity
            .internalServerError()
            .body("ERROR: " + TracerInfo.get().getCorrelationId());
    
    }
    

    Ps: fix compile error if any since I do not have IDE right now