Federico Piazza Federico Piazza - 2 months ago 24
Java Question

Spring boot not overriding Exception using @ControllerAdvice

I want to have a standard custom exception thrown from the controller advice aspect but for some reason my custom exception is not being caught by spring boot (1.3.3-RELEASE).

This is the code I have:

My Test

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(MyApplication.class)
@WebIntegrationTest("server.port:9000")
public class ControllerTest {
private final String URL = "http://localhost:9000";

@Test
public void testCustomExceptionResponse() {
// Invoke my controller generating some exception
Map error = restTemplate.getForObject(URL+"/exception/", Map.class);
assertTrue(error.get("exception").contains("MyCustomException"));
}
}


The controller

@RequestMapping(value = "/", method = RequestMethod.GET)
public List<Object> findAll() throws Exception {
// generate whatever exception here
if (1<2) throw new IllegalIdentifierException("test whatever exception");
return ccRepository.findByActive(true);
}


The GlobalExceptionHandler annotated with @ControllerAdvice

@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

// Catch whatever exception to throw my custom exception
@ExceptionHandler(Exception.class)
public void handleException(Exception ex) throws Exception {
logger.error("Exception Occured: ", ex);
throw new MyCustomException(ex.getLocalizedMessage());
}
}


I have debugged the code and the handleException method is being executed, however the weird thing is that MyCustomException is being thrown but the controller response is returning the original exception thrown:

{timestamp=1473963128439, status=500, error=Internal Server Error, exception=org.hibernate.metamodel.relational.IllegalIdentifierException, message=test whatever exception, path=/exception/}


I'm expecting to have something like this:

exception=com.myapp.MyCustomException


As far as I know, the controller advice is the generic way to catch all the exceptions in the controllers to have some logic tied (which in my case is the logger) and then I customized the unexpected exceptions by using a custom one.

Am I missing anything about how Spring Boot handles the exceptions?

Answer

That's not exactly the purpose of ControllerAdvice.

What is happening

  • you throw IllegalIdentifierException
  • you catch it in your ControllerAdvice
  • your handleException does not finish since you throw MyCustomException
  • this raises an exception in DispatcherServlet, with your original exception (IllegalIdentifierException) as root cause.

What you should do

The Controller Advice is meant to return a valid Http response in case of an error.

For instance, you could change it to:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) throws Exception {
        System.out.println("Exception Occured: " + ex);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Exception: " + ex.getLocalizedMessage());
    }
}

Thus:

  • the ControllerAdvice's method would complete successfully (returning a response);
  • The DispatcherServlet would be happy with you.
  • The original exception wouldn't be the root cause of a new error and is now swallowed successfully
  • the client receives the message "Exception: test whatever exception" with an HTTP Error code (I picked HttpStatus.INTERNAL_SERVER_ERROR).

You can test it with MockMvc:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Launcher.class)
@WebAppConfiguration
public class ControllerTest {
    @Autowired
    private WebApplicationContext webApplicationContext;

    @Test
    public void testCustomExceptionResponse() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

        mockMvc.perform(MockMvcRequestBuilders.get("/your/uri"))
        .andExpect(MockMvcResultMatchers.status().isInternalServerError())
        .andExpect(MockMvcResultMatchers.content().string("Exception: test whatever exception"));

        assertTrue(true);
    }
}