I'm having a bit of trouble getting a complete error handling solution. I've created a @ControllerAdvice class that catches Exceptions and QueryTimeException. However, the 404 Not Found message does not go to my custom page. And the HttpStatus.GATEWAY_TIMEOUT doesn't get routed properly. However, HttpStatus.REQUEST_TIMEOUT and Exceptions do get routed correctly.
Here's my Error Controller. What am I missing or doing incorrectly?
@ControllerAdvice
public class MyErrorController implements ErrorController {
public static final Logger logger = LoggerFactory.getLogger(MyErrorController.class);
@ExceptionHandler(HttpServerErrorException.GatewayTimeout.class)
@ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
public String gatewayTimeout() {
return "errors/error-timeout";
}
@ExceptionHandler(QueryTimeoutException.class)
@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
public String requestTimeout() {
return "errors/error-timeout";
}
@ExceptionHandler(value = {HttpClientErrorException.class, Exception.class})
public String handleExceptions() {
return "errors/error";
}
@RequestMapping("/error")
public String handleErrors(HttpServletRequest request, Exception ex) {
return "errors/error";
}
// This method is deprecated, so do not add the @Override annotation
// However, the method must remain while the Spring team has not yet removed it from the ErrorController interface
public String getErrorPath() {
return "/error";
}
}
I've added the following to application.properties:
server.error.whitelabel.enabled=true
server.error.path=/error
To test the above, I added the following methods to a Controller.
@GetMapping("/throw504")
public ResponseEntity<Void> throwGatewayTimeout() {
throw new HttpServerErrorException(HttpStatus.GATEWAY_TIMEOUT);
}
@GetMapping("/throw408")
public ResponseEntity<Void> throwResponseTimeout() {
throw new QueryTimeoutException();
}
@GetMapping("/throw500")
public ResponseEntity<Void> throwNotImplemented() {
throw new HttpServerErrorException(HttpStatus.NOT_IMPLEMENTED);
}
So I test by running my app, the navigating to localhost:8080/throw408 (I get the error-timeout page), localhost:8080/throw500 (I get the error page), which are both correct.
I then test localhost:8080/throw504 which incorrectly opens the error page, not error-timeout as I expected. I then test localhost:8080/jskld, which displays a generic 404 page, not the error page I expected.
Let's analyze this a bit more, and you will probably see the error in your code.
@ExceptionHandler(HttpServerErrorException.GatewayTimeout.class)
@ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
public String gatewayTimeout() {
return "errors/error-timeout";
}
The exception handler waits for an instance of GatewayTimeout.class to handle this exception.
When you create this exception in your controller though
@GetMapping("/throw504")
public ResponseEntity<Void> throwGatewayTimeout() {
throw new HttpServerErrorException(HttpStatus.GATEWAY_TIMEOUT); <------
}
If you inspect this class you will find
public class HttpServerErrorException extends HttpStatusCodeException {
private static final long serialVersionUID = -2915754006618138282L;
public HttpServerErrorException(HttpStatus statusCode) {
super(statusCode);
}
So the constructor that you use returns back a HttpServerErrorException.class
Not a GatewayTimeout.class
GatewayTimeout.class is a static nested class which extends HttpServerErrorException.class.
public static final class GatewayTimeout extends HttpServerErrorException {
private GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) {
super(HttpStatus.GATEWAY_TIMEOUT, statusText, headers, body, charset);
}
If you look closer in HttpServerErrorException.class there is a constructor that creates an instance of GatewayTimeout.class
One way would be to use the constructor
HttpServerErrorException.create(HttpStatus.GATEWAY_TIMEOUT,"status text", new HttpHeaders(), new byte[] {}, Charset.defaultCharset());
This will return you an exception instance of GatewayTimeout.class which spring would probably be able to match.
So your controller must be adapted to be something like
@GetMapping("/throw504")
public ResponseEntity<Void> throwGatewayTimeout() {
throw HttpServerErrorException.create(HttpStatus.GATEWAY_TIMEOUT,"status text", new HttpHeaders(), new byte[] {}, Charset.defaultCharset());
}
In the end the point is that GatewayTimeout.class extends from HttpServerErrorException. So this means that GatewayTimeout.class is an HttpServerErrorException, but an HttpServerErrorException would not be for sure a GatewayTimeout.class.