We're running DropWizard and trying to drop in logging of the URLs that cause 404 responses to be thrown
We have a catchall exception mapper that receives a NotFoundException. Frustratingly that exception doesn't carry context of which URL caused it to be thrown.
Example application here: https://github.com/pauldambra/not.found.example
We're using an ExceptionMapper
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
logger.error(urlFrom(exception), exception);
return Response.status(404).build();
}
private String urlFrom(final NotFoundException exception) {
return "why is this not a property on the exception?!";
}
private class ExampleLogger {
void error(final String notFoundUrl, final NotFoundException exception) {
System.out.println("someone tried to load " + notFoundUrl);
System.out.println(exception.getMessage());
}
}
}
If we look at the application logs when someone requests a URL that the application doesn't serve we see that the application can log that it is returning a 404 for a path but our custom logger has no access to the URL
someone tried to load why is this not a property on the exception?!
HTTP 404 Not Found
127.0.0.1 - - [08/May/2019:09:53:47 +0000] "GET /ping/pong HTTP/1.1" 404
Is an ExceptionMapper the wrong way to do this?
Turns out there are two ways
one does use the exception mapper:
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
// magically inject a thing
// remember that magic is for evil wizards
#Context
private HttpServletRequest request;
private ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
final StringBuffer absolutePath = HttpUtils.getRequestURL(request);
logger.error("exception mapper: " + absolutePath, exception);
return Response.status(404).build();
}
}
This works but isn't very discoverable.
You can also add a response filter
public class NotFoundLoggingFilter implements ContainerResponseFilter {
private ExampleLogger logger = new ExampleLogger();
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
if (responseContext.getStatus() != 404) {
return;
}
final URI absolutePath = requestContext.getUriInfo().getAbsolutePath();
logger.error("filter: " + absolutePath, new NotFoundException());
}
}
This doesn't require any magic so suits me but you can choose your poison.
Really the path should be on the NotFoundException - if I had more time I'd propose the code change to add it.
Trying to implement returning the customized denied message for consuming angular clients
*PermissionEvaluator has the implementation to authorize particular fieldName (Lets say user clicks on the button, he has to get the customized message fetched from DB)
Question is :
hasPermission was returning the html page with "Access Denied" Message
Is it possible to return customized message to the client
Message -> { statusCode, message, actionName }
#PreAuthorize("#PermissionEvaluator.hasPermission(authentication, #requestModel, 'updateSync')")
#RequestMapping(value = "/api/updateMarketValueSelected", method = RequestMethod.POST)
public ResponseEntity<List<InputFormModel>> updateAndSaveMarketValueSelected(#RequestBody RequestModel requestModel) throws ParseException {
Tried solving this problem by
#Component
#ControllerAdvice
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
...
#ExceptionHandler(value = { MyAccessDeniedException.class })
public void commence(HttpServletRequest request, HttpServletResponse response, MyAccessDeniedException ex ) throws IOException {
String json = new ObjectMapper().writeValueAsString(ex.getRequestModel());
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(json);
response.flushBuffer();
}
I have the following controller:
#Controller
#RequestMapping("/my-account")
public class AccountController {
#RequestMapping(value = "/foo/post",
method = RequestMethod.POST)
public String doPost(final RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("flashAttribute", "flashAttributeValue");
return "redirect:/my-account/foo/get";
}
#RequestMapping(value = "/foo/get",
method = RequestMethod.GET)
public void doGet(final HttpServletRequest request, final Model model) {
System.out.println("in request: " + RequestContextUtils.getInputFlashMap(request).get("flashAttribute"));
System.out.println("in model: " + model.asMap().get("flashAttribute"));
}
}
I would also like to access the flash attribute flashAttribute during the invocation of a filter in the filter chain that finally invokes springs default DispatcherServlet which in turn invokes AccountController.
public class FlashAttributeBasedFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
String flashAttribute = // how to access the redirectAttribute flashAttribute here?
// do something with flashAttribute ...
filterChain.doFilter(request, response);
}
The DispatcherServlet uses a org.springframework.web.servlet.FlashMapManager that handles these flash attributes, but it doesn't provide read-only access so I think I would be messing something up if I would use it in the filter. And also the FlashMapManager instance is kept in the dispatcher servlet privately.
Does anybody have an idea how I can make the redirect attribute accessible in the filter chain for the GET request succeeding the POST?
Considering that all these methods return null into my filter (I don't understand why):
RequestContextUtils.getFlashMapManager(httpRequest)
RequestContextUtils.getInputFlashMap(httpRequest)
RequestContextUtils.getOutputFlashMap(httpRequest)
I used a drastic solution: read directly the into the session (where flash attributes are stored).
CopyOnWriteArrayList<FlashMap> what = (CopyOnWriteArrayList<FlashMap>) httpRequest.getSession().getAttribute("org.springframework.web.servlet.support.SessionFlashMapManager.FLASH_MAPS");
if (what != null) {
FlashMap flashMap = what.get(0);
[read flashMap as you read a HashMap]
}
I know, this code is super ugly but at the moment I don't find another solution.
Had the same problem, following works for me.
FlashMap flashMap = new SessionFlashMapManager().retrieveAndUpdate(request, null);
flashMap.get("parameter");
I'm using Spring boot for hosting a REST API. Instead of having the standard error response I would like to always send a JSON response even if a browser is accessing the URL and as well a custom data structure.
I can do this with #ControllerAdvice and #ExceptionHandler for custom exceptions. But I can't find any good ways of doing this for standard and handled errors like 404 and 401.
Are there any good patterns of how to do this?
For those Spring Boot 2 users who don't wanna use #EnableWebMvc
application.properties
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
ControllerAdvice
#RestControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public HashMap<String, String> handleNoHandlerFound(NoHandlerFoundException e, WebRequest request) {
HashMap<String, String> response = new HashMap<>();
response.put("status", "fail");
response.put("message", e.getLocalizedMessage());
return response;
}
}
Source
It is worked for me in case of #RestControllerAdvice with spring boot
spring.mvc.throw-exception-if-no-handler-found=true
server.error.whitelabel.enabled=false
spring.resources.add-mappings=false
#RestControllerAdvice
public class ErrorHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND )
public String handleNotFoundError(NoHandlerFoundException ex) {
return "path does not exists";
}
}
I've provided the sample solution on how to override response for 404 case. The solution is pretty much simple and I am posting sample code but you can find more details on the original thread: Spring Boot Rest - How to configure 404 - resource not found
First: define Controller that will process error cases and override response:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Second: you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Summing up all answers and comment, I think the best way to do this is-
First, tell spring boot to throw exception in case of no handler found in application.properties
spring.mvc.throw-exception-if-no-handler-found=true
Then handle NoHandlerFoundException in your application. I handle this by following way
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
logger.error("URL not found exception: " + ex.getRequestURL());
prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
}
}
If you are using Swagger then you can view my other answer to exclude swagger URL from this exception handler
404 error is handled by DispatcherServlet. there is a property throwExceptionIfNoHandlerFound, which you can override.
In Application class you can create a new bean:
#Bean
DispatcherServlet dispatcherServlet () {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
...and then catch the NoHandlerFoundException exception in
#EnableWebMvc
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
doSomething(LOG.debug("text to log"));
}
}
You may extend the ResponseEntityExceptionHandler class, which include a lot of common exceptions in a Spring Boot Project. For example, if you wish to use a custom handler for binding exceptions, you may use the following,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"key\":\"value\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_ACCEPTABLE, request);
}
}
An other example for the http status 404-Not Found,
#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);
}
}
Regarding the 404 not found exception you should configure the DispatcherServlet to throw and exception if it doesn't find any handlers, instead of the default behavior. For issues with 404, you may also read this question.
I was having the same issue but fixed it using a different method.
To return 404, 401 and other status in a custom response, you can now add the response status to the custom exception class and call it from your exception handler.
With spring utility class AnnotationUtils, you can get the status of any of the defined custom exceptions with the findAnnotation method and it will return the appropriate status using whatever annotation you defined for the exceptions including not found.
Here's my #RestControllerAdvice
#RestControllerAdvice
public class MainExceptionHandler extends Throwable{
#ExceptionHandler(BaseException.class)
ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException e)
{
ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
if(status != null)
{
return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
}
}
CustomParamsException to return Bad request status
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
private static final String CODE = "400";
public CustomParamsException(String message) {
super(CODE, message);
}
}
Details not found to return Not Found Status
#ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
private static final String CODE = "400";
public DetailsNotException(String message) {
super(CODE, message);
}
}
A GeneralMainException to extend Excetion
public class GeneralMainException extends Exception {
private String code;
private String message;
public GeneralMainException (String message) {
super(message);
}
public GeneralMainException (String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
#Override
public String getMessage() {
return message;
}
}
You can decide to handle other system exceptions by including it to the controller advice.
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
ExceptionErrorResponse sysError(Exception e)
{
return new ExceptionErrorResponse(""1002", e.getMessage());
}
It seems that you need to introduce an appropriately annotated method, e.g. for unsupported media type (415) it will be:
#ExceptionHandler(MethodArgumentNotValidException)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
logger.error('Caught exception', e)
def response = new ExceptionResponse(
error: 'Validation error',
exception: e.class.name,
message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
path: req.servletPath,
status: BAD_REQUEST.value(),
timestamp: currentTimeMillis()
)
new ResponseEntity<>(response, BAD_REQUEST)
}
However it may not be possible since 401 and 404 may be thrown before they reach DispatcherServlet - in this case ControllerAdvice will not work.
You can add custom ErrorPage objects which correlate to the error-page definition in web.xml. Spring Boot provides an example...
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
}
}
EDIT: While I think the method above will work if you make the error pages rest controllers, an even easier way would be to include a custom ErrorController like the one below...
#Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
return new CustomErrorController(errorAttributes);
}
// ...
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#Override
#RequestMapping(value = "${error.path:/error}")
#ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
ResponseEntity<Map<String, Object>> error = super.error(request);
HttpStatus statusCode = error.getStatusCode();
switch (statusCode) {
case NOT_FOUND:
return getMyCustomNotFoundResponseEntity(request);
case UNAUTHORIZED:
return getMyCustomUnauthorizedResponseEntity(request);
default:
return error;
}
}
}
Please see Spring Boot REST service exception handling. It shows how to tell the dispatcherservlet to emit exceptions for "no route found" and then how to catch those exceptions. We (the place I work) are using this in production for our REST services right now.
Starting with Spring version 5 can use class ResponseStatusException:
#GetMapping("example")
public ResponseEntity example() {
try {
throw new MyException();
} catch (MyException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "My Exception", e);
}
}
I wanted to have the same error format (json) structure across all possible error scenarios, so I just registered my own ErrorController reusing the code from AbstractErrorController:
#Controller
#RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public class ErrorController extends AbstractErrorController {
public ErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toUnmodifiableList()));
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
final var status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
return new ResponseEntity<>(getErrorAttributes(request, ErrorAttributeOptions.defaults()), status);
}
#Override
public String getErrorPath() {
return null;
}
}
with this you dont need any controller advice, all errors go to error method by default
I am using #ExceptionHandler in Spring mvc 3 application for exception handling and I have written a controller like this :
#Controller
public class ExceptionsController {
private static final Logger logger = Logger.getLogger(ExceptionsController.class);
#ExceptionHandler(IOException.class)
public ModelAndView handleIOException(IOException ex) {
logger.info("handleIOException - Catching: " + ex.getClass().getSimpleName());
return errorModelAndView(ex);
}
private ModelAndView errorModelAndView(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("name", ex.getClass().getSimpleName());
return modelAndView;
}
#ExceptionHandler({NullPointerException.class, NoSuchRequestHandlingMethodException.class})
public ModelAndView handleExceptionArray(Exception ex) {
logger.info("handleExceptionArray - Catching: " + ex.getClass().getSimpleName());
return errorModelAndView(ex);
}
#ExceptionHandler(DataFormatException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "My Response Status Change....!!")
public ModelAndView handleDataFormatException(DataFormatException ex, HttpServletResponse response) {
logger.info("Handlng DataFormatException - Catching: " + ex.getClass().getSimpleName());
return errorModelAndView(ex);
}
For this, I have configured spring servlet xml file with bean declaration of AnnotationMethodHandlerExceptionResolver
What I want is when a user modifies the URL, PageNotFound Exception should be handled by #ExceptionHandler.
My scenario is, in browser, when a user changes the URL manually that is not correct, in that case a 404 page is rendered by the server, in place of that I want to show an error page as it comes in Facebook
You don't need to use Spring for that. The 404 is created by your app server (e.g Tomcat). Depending on how your DispatcherServlet is set up, you can just add this to your web.xml to return a custom page:
<error-page>
<error-code>404</error-code>
<location>/error.html</location>
</error-page>
Just make sure /error.html is either served by your app server (not by the DispatcherServlet) or as a pass-through from a file by Spring.