Spring boot - taking control of 404 Not Found
Asked Answered
E

5

6

I'm trying to figure out the simplest way to take control over the 404 Not Found handler of a basic Spring Boot RESTful service such as the example provided by Spring:

https://spring.io/guides/gs/rest-service/

Rather than have it return the default Json output:

{
  "timestamp":1432047177086,
  "status":404,
  "error":"Not Found",
  "exception":"org.springframework.web.servlet.NoHandlerFoundException",
  "message":"No handler found for GET /aaa, ..."
}

I'd like to provide my own Json output.

By taking control of the DispatcherServlet and using DispatcherServlet#setThrowExceptionIfNoHandlerFound(true), I was able to make it throw an exception in case of a 404 but I can't handle that exception through a @ExceptionHandler, like I would for a MissingServletRequestParameterException. Any idea why?

Or is there a better approach than having a NoHandlerFoundException thrown and handled?

Esbenshade answered 19/5, 2015 at 15:13 Comment(1)
May be this will be helpful for you.Drawing
A
5

According to the Spring documentation appendix A. there is a boolean property called spring.mvc.throw-exception-if-no-handler-found which can be used to enable throwing NoHandlerFoundException. Then you can create exception handler like any other.

@RestControllerAdvice
class MyExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public String handleNoHandlerFoundException(NoHandlerFoundException ex) {
        log.error("404 situation detected.",ex);
        return "Specified path not found on this server";
    }
}

@ExceptionHandler itself without @ControllerAdvice (or @RestControllerAdvice) can't be used, because it's bound to its controller only.

Anthropolatry answered 24/2, 2017 at 12:37 Comment(0)
T
5

It works perfectly Fine.

When you are using SpringBoot, it does not handle (404 Not Found) explicitly; it uses WebMvc error response. If your Spring Boot should handle that exception, then you should do some hack around Spring Boot. For 404, the exception class is NoHandlerFoundException; if you want to handle that exception in your @RestControllerAdvice class, you must add @EnableWebMvc annotation in your Application class and set setThrowExceptionIfNoHandlerFound(true); in DispatcherServlet. Please refer to the following code:

@SpringBootApplication
@EnableWebMvc
public class Application {  
    @Autowired
    private DispatcherServlet servlet;

    public static void main(String[] args) throws FileNotFoundException, IOException {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    public CommandLineRunner getCommandLineRunner(ApplicationContext context) {
        servlet.setThrowExceptionIfNoHandlerFound(true);
        return args -> {};
    }
}

After this you can handle NoHandlerException in your @RestControllerAdvice class

@RestControllerAdvice
public class AppException {

    @ExceptionHandler(value={NoHandlerFoundException.class})
    @ResponseStatus(code=HttpStatus.BAD_REQUEST)
    public ApiError badRequest(Exception e, HttpServletRequest request, HttpServletResponse response) {
        e.printStackTrace();
        return new ApiError(400, HttpStatus.BAD_REQUEST.getReasonPhrase());
    }
}   

I have created ApiError class to return customized error response

public class ApiError {
    private int code;
    private String message;
    public ApiError(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public ApiError() {
    }   
    //getter & setter methods...
}
Triboelectricity answered 20/8, 2017 at 17:36 Comment(1)
adding @EnableWebMvc should totally switch off Spring Boot's auto-configuration for Spring MVC - so all spring.mvc.* configuration properties will be ignored. See the reference documentation docs.spring.io/spring-boot/docs/current/reference/html/….Dunford
C
2

In short, the NoHandlerFoundException is thrown from the Container, not from your application within your container. Therefore your Container has no way of knowing about your @ExceptionHandler as that is a Spring feature, not anything from the container.

What you want is a HandlerExceptionResolver. I had the very same issue as you, have a look at my solution over there: How to intercept "global" 404s on embedded Tomcats in spring-boot

Chilpancingo answered 3/8, 2015 at 15:3 Comment(0)
O
1

The @EnableWebMvc based solution can work, but it might break Spring boot auto configurations. The solution I am using is to implement ErrorController:

@RestController
@RequiredArgsConstructor
public class MyErrorController implements ErrorController {

        private static final String ERROR_PATH = "/error";

        @NonNull
        private final ErrorAttributes errorAttributes;

        @RequestMapping(value = ERROR_PATH)
        Map<String, Object> handleError(WebRequest request) {
            return errorAttributes.getErrorAttributes(request, false);
        }

        @Override
        public String getErrorPath() {
            return ERROR_PATH;
        }
}
Ongun answered 5/9, 2018 at 7:14 Comment(0)
A
0

The solution for this problem is:

  • Configure DispatcherServlet to throw and exception if it doesn't find any handlers.
  • Provide your implementation for the exception that will be thrown from DispatcherServlet, for this case is the NoHandlerFoundException.

Thus, in order to configure DispatcherServlet you may use properties file or Java code.
Example for properties.yaml,

spring:
    mvc:
      throw-exception-if-no-handler-found: true

Example for properties.properties,

spring.mvn.throw-exception-if-no-handler-found=true

Example for Java code, we just want to run the command servlet.setThrowExceptionIfNoHandlerFound(true); on startup, I use the InitializingBean interface, you may use another way. I found a very well written guide to run logic on startup in spring from baeldung.

@Component
public class WebConfig implements InitializingBean {

    @Autowired
    private DispatcherServlet servlet;

    @Override
    public void afterPropertiesSet() throws Exception {
        servlet.setThrowExceptionIfNoHandlerFound(true);
    }

}

Be careful! Adding @EnableWebMvc disables autoconfiguration in Spring Boot 2, meaning that if you use the annotation @EnableWebMvc then you should use the Java code example, because the spring.mvc.* properties will not have any effect.

After configuring the DispatcherServlet, you should override the ResponseEntityExceptionHandler which is called when an Exception is thrown. We want to override the action when the NoHandlerFoundException is thrown, like the following example.

@ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
        headers.add("Content-Type", "application/json;charset=utf-8");
        return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
    }
}

Finally, adding a break point to method handleException of ResponseEntityExceptionHandler might be helpful for debugging.

Alemanni answered 22/1, 2021 at 11:51 Comment(1)
spring.resources.add-mappings=false I have to add this for 404 in spring bootAnn

© 2022 - 2024 — McMap. All rights reserved.