How to show custom 404 page when user enters invalid URL in spring boot application?
Solution 1
First off, define an exception for each especial HTTP error you're going to handle. Here i'm just defining one for handling 404 Not Found
case:
public class NotFoundException extends RuntimeException {}
To replace the default Spring Boot's error handling mechanism completely, we can implement ErrorController
. Instead of just implementing the ErrorController
, here i'm extending AbstractErrorController
, which implements the ErrorController
and provides some auxiliary methods like getStatus()
.
Anyway, the basic idea is to handle all errors using an endpoint, say /error
, and throw those pre-defined exceptions in case of their corresponding HTTP status codes:
@Controller
public class CustomErrorController extends AbstractErrorController {
private static final String ERROR_PATH= "/error";
@Autowired
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
/**
* Just catching the {@linkplain NotFoundException} exceptions and render
* the 404.jsp error page.
*/
@ExceptionHandler(NotFoundException.class)
public String notFound() {
return "404";
}
/**
* Responsible for handling all errors and throw especial exceptions
* for some HTTP status codes. Otherwise, it will return a map that
* ultimately will be converted to a json error.
*/
@RequestMapping(ERROR_PATH)
public ResponseEntity<?> handleErrors(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status.equals(HttpStatus.NOT_FOUND))
throw new NotFoundException();
return ResponseEntity.status(status).body(getErrorAttributes(request, false));
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}
Of course, this solution is only suitable for Traditional Deployment. If you're planning to use an Embedded Servlet Container, you're better off defining an EmbeddedServletContainerCustomizer
.
Solution 2
Whould you like to add this @Bean
to your spring application config, like this :
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error404.html");
container.addErrorPages(error404Page);
}
};
}
and put error404.html
in your static folder.
Reference : http://www.sporcic.org/2014/05/custom-error-pages-with-spring-boot/
Solution 3
Here you go
@RestController
public class RestErrorController implements ErrorController{
private static final String PATH = "/error";
@Autowired
private ErrorAttributes errorAttributes;
@RequestMapping(value=PATH,method=RequestMethod.GET)
public ApiErrorExtended error(HttpServletRequest request, HttpServletResponse response){
return new ApiErrorExtended( response.getStatus(),getErrorAttributes(request, true));
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
public String getErrorPath() {
return PATH;
}
}
This is my rest error controller. You can modify it to return whatever u want.
ashishjmeshram
Updated on June 26, 2022Comments
-
ashishjmeshram almost 2 years
I am using java config in spring boot, spring security application. I have configured the error controller as below. But whenever, I enter an invalid URL it goes to
error.jsp
, which is configured to handle the errors in the application:@Controller public class AppErrorController implements ErrorController { private static final String PATH = "/error"; @RequestMapping(value = "/pageNotFound", method = { RequestMethod.GET, RequestMethod.POST }) public String pageNotFound() { return "pageNotFound"; } @RequestMapping(value = "/accessDenied", method = { RequestMethod.GET, RequestMethod.POST }) public String accessDenied() { return "accessDenied"; } @RequestMapping(value = PATH) public String error() { return "error"; } @Override public String getErrorPath() { return PATH; } }
web.xml
<error-page> <error-code>404</error-code> <location>/pageNotFound</location> </error-page> <error-page> <error-code>500</error-code> <location>/error</location> </error-page>
404 is never thrown when the invalid URL is entered.
Also, I am not using embedded tomcat. I am deploying war to the external tomcat. I have this in my
application.yml
file:server: error: whitelabel: enabled: false
Below is the log in case if I enter a invalid URL. There is no error. It just redirects to
/error
in case of invalid URL:2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter : Bound request context to thread: org.apache.catalina.connector.RequestFacade@371ef2a3 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/donotexisturl'; against '/static/**' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/donotexisturl'; against '/i18n/**' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@b8db0c9d: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@6e929f31 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /donotexisturl' doesn't match 'POST /logout 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter' 2016-03-15 10:02:45.061 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /donotexisturl' doesn't match 'POST /checklogin 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 7 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.AnonymousAuthenticationFilter : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /donotexisturl' doesn't match 'POST /logout 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/donotexisturl'; against '/login**' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/donotexisturl'; against '/error**' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/donotexisturl'; against '/checklogin**' 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /doNotExistURL; Attributes: [fullyAuthenticated] 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'} 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6ad88564, returned: 1 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter@19007bb6, returned: 0 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.access.vote.UnanimousBased : Voter: com.sts.app.core.user.security.AccessDecisionVoterImpl@1465b821, returned: 1 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /doNotExistURL reached end of additional filter chain; proceeding with original chain 2016-03-15 10:02:45.062 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/gen/doNotExistURL] 2016-03-15 10:02:45.063 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /doNotExistURL 2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/doNotExistURL] 2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/doNotExistURL] are [/**] 2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/doNotExistURL] are {} 2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/doNotExistURL] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@87dd2cb]]] and 1 interceptor 2016-03-15 10:02:45.064 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/gen/doNotExistURL] is: -1 2016-03-15 10:02:45.077 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet : Successfully completed request 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@371ef2a3 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.b.c.web.OrderedRequestContextFilter : Bound request context to thread: org.apache.catalina.core.ApplicationHttpRequest@20ae74b1 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/static/**' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/i18n/**' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@b8db0c9d: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /error' doesn't match 'POST /logout 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter' 2016-03-15 10:02:45.078 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /error' doesn't match 'POST /checklogin 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 7 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.s.w.a.AnonymousAuthenticationFilter : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b8db0c9d: Principal: User{id=8, firstname='Adam', lastname='Milne', email='[email protected]', roleId=1}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: FED1F23633671F6E257CA9C5AFCEE216; Granted Authorities: ModuleOperation{moduleOperationId=1, moduleOperationName='roleList', moduleId=2, moduleName='role'}, ModuleOperation{moduleOperationId=8, moduleOperationName='deleteUser', moduleId=1, moduleName='user'}' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.security.web.FilterChainProxy : /error reached end of additional filter chain; proceeding with original chain 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/gen/error] 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /error 2016-03-15 10:02:45.079 DEBUG 9997 --- [io-8080-exec-22] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public java.lang.String com.sts.app.core.common.web.AppErrorController.error()]
-
SeaBiscuit about 8 yearsTry value=404 instead /pageNotFound see if that helps edited : value = /error
-
SeaBiscuit about 8 yearsDid u try removing
<error-page> <error-code>404</error-code> <location>/pageNotFound</location> </error-page> <error-page> <error-code>500</error-code> <location>/error</location> </error-page>
all together? I believe 404 will be resolved by default. Anyways i am gonna take a closer look at my config. when i get back to my pc. -
ashishjmeshram about 8 yearsI am not using embedded tomcat. Will this still apply?
-
ekem chitsiga about 8 yearsWhat excpetion do you get when your remove the error pages in your web.xml.Probably Spring is throwing another exception wihich is not 404 due to a setup error. Post the error being displayed on tomcat`s default error page
-
ashishjmeshram about 8 yearsI have tried that also. Does not work. Actually, it used to work in other non spring boot apps.
-
avojak almost 6 yearsIronically that link is now dead.
-
Shailesh Vikram Singh over 2 yearsEmbeddedServletContainerCustomizer is not available in Spring Boot 2