Spring Boot 2.2.5 404 page not found custom json response - spring

How can I have a custom json for my 404 pages ?
actually what I need is to be able to create custom json errors for my application.
for example for 404,401,403,422, ...
I searched a lot and what I found is :
package ir.darsineh.lms.http.exceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public void springHandleNotFound(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value());
}
}
and here is the error I get :
Ambiguous #ExceptionHandler method mapped for [class org.springframework.web.servlet.NoHandlerFoundException]
I need my api response body json to be something like this :
{"code": 404, "message": "page not found"}

First, you should let Spring MVC to throw exception if no handler is found:
spring.mvc.throw-exception-if-no-handler-found=true
Then, the exception must be caught using a #ControllerAdvice:
#ControllerAdvice
public class CustomAdvice {
// 404
#ExceptionHandler({ NoHandlerFoundException.class })
#ResponseBody
#ResponseStatus(HttpStatus.NOT_FOUND)
public CustomResponse notFound(final NoHandlerFoundException ex) {
return new CustomResponse(HttpStatus.NOT_FOUND.value(), "page not found");
}
}
#Data
#AllArgsConstructor
class CustomResponse {
int code;
String message;
}
Do not forget to add #EnableWebMvc annotation to your app.

ResponseEntityExceptionHandler class already has handleNoHandlerFoundException() method defined as below.
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
Since the method signatures (parent class and our implementation class) are different, it resulted in ambiguous error. Using the same signature will override the above method with our custom implementation.
#ExceptionHandler(NoHandlerFoundException.class)
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ErrorResponse error = new ErrorResponse("404", "page not found");
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
Hope this helps!!

Related

#ControllerAdvice never gets triggered in Spring Boot

I am trying to create my own custom response for all types of RestClientResponseException in my Spring Boot Application
Custom exception thrown from Controller class :
throw new HealthCheckRestException(ex.getResponseBodyAsString());
My ExceptionHandler class goes like this :
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebMvc
#ControllerAdvice
public class AvailExceptionHandler {
private static final Logger logger = Logger.getLogger(AvailExceptionHandler.class);
#ExceptionHandler(value=HealthCheckRestException.class)
public AvailResponse handleHttpErrorResponse(final HealthCheckRestException ex, final HttpServletRequest request){
logger.error("RestClientException is thrown from HealthCheck API: with status :"
+ ex.getStatusText() + " and exception : "+ ex.getMessage());
return new AvailResponse(AvailStatus.ERROR);
}
}
I have tried all possible cases like :
1) Trying #ExceptionHandler inside the controller class
2) Including #ControllerAdvice(basePackages = "com.org.availabillity.utilities") to scan for specific packages where the controller is defined.
3) Using #Order(Ordered.HIGHEST_PRECEDENCE) to set precedence
4) Using #RestControllerAdvice
Nothing seems to intercept after the exception is thrown and call the method annotated with #ExceptionHandler
Stuck for sometime now on this and need some help.
Much appreciate your help on this.
I am using spring-web-5.0.6.RELEASE
Try with something like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Map;
#RestControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public final class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final boolean INCLUDE_STACKTRACE = false;
#ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handle(final WebRequest request, final Throwable exception) {
log.debug("Fallback handler executed for unhandled exception:", exception);
return new ResponseEntity<>(new DefaultErrorAttributes().getErrorAttributes(request, INCLUDE_STACKTRACE),
HttpStatus.INTERNAL_SERVER_ERROR);
}
// IllegalArgumentException is not "entirely correct", so replace this with your own
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handle1(final WebRequest request, final Throwable exception) {
log.debug("Fallback handler executed for unhandled exception:", exception);
return new ResponseEntity<>(new DefaultErrorAttributes().getErrorAttributes(request, INCLUDE_STACKTRACE),
HttpStatus.INTERNAL_SERVER_ERROR);
}
// TODO: Keep adding exception(s) handlers for particular cases
}
GlobalExceptionHandler is just a Spring component/bean that can be placed anywhere, provided you configured that location to be discovered.
#EnableWebMvc should be placed in a class annotated with #Configuration. Moreover, if you are using Spring Boot, chances are you don't need it because it will be inferred.
Here is the small example from my Handler class;
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({QueryNotFoundException.class,ContentNotAllowedException.class})
public final ResponseEntity<ApiError> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof QueryNotFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
QueryNotFoundException qnfe = (QueryNotFoundException) ex;
return handleQueryNotFoundException(qnfe, headers, status, request);
} else if (ex instanceof ContentNotAllowedException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
ContentNotAllowedException cnae = (ContentNotAllowedException) ex;
return handleContentNotAllowedException(cnae, headers, status, request);
} else {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleExceptionInternal(ex, null, headers, status, request);
}

overridden handleMethodArgumentNotValid method of ResponseEntityExceptionHandler not called

I am trying to have a custom validator and also an ExceptionHandler for my spring boot rest service and when I added ExceptionHandler, the validation errors are not being sent to the UI. So I tried to override handleMethodArgumentNotValid method and that does not work either. Can someone give some insight into this?
This is how I have configured my validation class in the controller -
package services.rest.controller;
import java.io.IOException;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import services.rest.model.TestInput;
import services.rest.validator.DataValidator;
#RestController
#RequestMapping("/test")
#Slf4j
public class RestResource {
#Autowired
private DataValidator validator;
#PostMapping("/create")
public String create(#Valid final TestInput input) throws IOException {
log.debug(input.toString());
return "Success";
}
#InitBinder()
public void init(final WebDataBinder binder) {
binder.addValidators(validator);
}
}
This is my ExceptionHandler code -
package services.rest.exceptionhandler;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(final Exception ex, final WebRequest request) {
System.out.println("All exceptions Method getting executed!!!!");
final List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
return new ResponseEntity("Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex,
final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
System.out.println("Validation Error Method getting executed!!!!");
final List<String> details = new ArrayList<>();
for (final ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
return new ResponseEntity("Validation Error", HttpStatus.BAD_REQUEST);
}
}
Initially did not override "handleMethodArgumentNotValid" method. Now after overriding it too, it does not work
Did you check the stack trace, it can be possible that instead of MethodArgumentNotValid exception, ConstraintViolation exception is getting raised. Spring doen not provide any default handler for that.
I tested your example and seems to work. Would be helpful if you would also post TestInput and DataValidator.
Doesn't work doesn't say precisely what happened, my guess is that you just received a 400 status code. If that is the case it might be just because the validation is trigger before but you did not override ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
The following approach worked for me:
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptions(final Exception ex, final WebRequest request) {
System.out.println("All exceptions Method getting executed!!!!");
final List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
return new ResponseEntity("Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MissingServletRequestParameterException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(final Exception ex, final WebRequest request) {
System.out.println("Validation Error Method getting executed!!!!");
final List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
return new ResponseEntity("Validation Error", HttpStatus.BAD_REQUEST);
}
}
Basically what I did was:
Not extending from ResponseEntityExceptionHandler class.
Put the #Order(Ordered.HIGHEST_PRECEDENCE).
Create a handler for the exception MissingServletRequestParameterException.
Hope this help you
Define your exception handler pakcage in #ComponentScan in App class.
#SpringBootApplication
#ComponentScan(basePackages = { "services.rest.exceptionhandler" })
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

Get exception object in custom error controller

I am using spring boot and write a global exception handler use AbstractErrorController. How could i get an exception object in controller?
#Controller
public class MyCustomErrorController extends AbstractErrorController {
public MyCustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#RequestMapping("/error")
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
Exception e = ...; // how to get exception here
log.error(e);
displayError(req, resp, e);
}
#Override
public String getErrorPath() {
return "/error";
}
}
You can get the exception from the HttpServletRequest as follows:
#Controller
public class MyCustomErrorController extends AbstractErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
...
}
}
An handler intercepts an Exception generated or re-thrown by a controller. It doesn't have an endpoint because it usually does it for all the controllers in your application. The Handler instructs the application server to return a specific error when a specific Exception is thrown.
Here is an example:
#ControllerAdvice // Specialization of #Component for classes that declare #ExceptionHandler, #InitBinder, or #ModelAttribute methods to be shared across multiple #Controller classes.
public class ResourceNotFoundExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
ResponseEntity<Object> response = new ResponseEntity<>(error, headers, HttpStatus.NOT_FOUND);
return response;
}
}
In this example ApiError is a data structure that reports the error to the UI. What this code does is intercepting the Exception "ResourceNotFoundException", create an appropriate error Data transfer object, set the response HttpStatus and headers and return the error.
you can find a different example here: https://github.com/otrebor/springbootseed-openshift/blob/master/src/main/java/com/company/example/springbootseed/core/errorhandling/handlers/
Add Exception as an extra parameter to handleError()

Flash Attribute in custom AuthenticationFailureHandler

On login failure I want to redirect the user to an error page and display a meaningful error message. Is it possible to add Flash Attributes that will be passed to the subsequent request?
The code presented below doesn't work. RequestContextUtils.getOutputFlashMap() returns null.
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler
{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
{
FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
if (outputFlashMap != null)
{
outputFlashMap.put("error", "Error message");
}
response.sendRedirect(request.getContextPath() + "/error");
}
}
I encountered this same problem with Spring 4.3.17 and finally found a solution by stepping through the spring-webmvc code and making educated guesses about how to integrate Flash Attributes outside the normal framework. SessionFlashMapManager is the key to getting this to work. I believe this method should work for Spring 3.1.1+.
package org.myorg.spring.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.support.SessionFlashMapManager;
#Component
public final class FlashAuthenticationFailureHandler implements AuthenticationFailureHandler
{
/**
* Flash attribute name to save on redirect.
*/
public static final String AUTHENTICATION_MESSAGE = "FLASH_AUTHENTICATION_MESSAGE";
public FlashAuthenticationFailureHandler()
{
return;
}
#Override
public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception) throws IOException, ServletException
{
if (exception != null)
{
final FlashMap flashMap = new FlashMap();
// Don't send the AuthenticationException object itself because it has no default constructor and cannot be re-instantiated.
flashMap.put(AUTHENTICATION_MESSAGE, exception.getMessage());
final FlashMapManager flashMapManager = new SessionFlashMapManager();
flashMapManager.saveOutputFlashMap(flashMap, request, response);
}
response.sendRedirect(request.getHeader("referer"));
return;
}
}
Then in the controller(s) that requires the flash attribute, simply add a ModelAttribute with the same name:
#RequestMapping(value = {"/someview"}, method = RequestMethod.GET)
public String getSomePage(final Authentication authentication, #ModelAttribute(FlashAuthenticationFailureHandler.AUTHENTICATION_MESSAGE) final String authenticationMessage, final Model model) throws Exception
{
if (authenticationMessage != null)
{
model.addAttribute("loginMessage", authenticationMessage);
}
return "myviewname";
}
Then the page attribute containing the message can be accessed in your JSP as follows:
<c:if test="${not empty loginMessage}">
<div class="alert alert-danger"><c:out value="${loginMessage}" /></div>
</c:if>
I'd guess it's null because you are calling the function from the filter chain, whereas the flash map is maintained by Spring's DispatcherServlet which the request hasn't passed through at this point.
Why not just use a parameter? i.e
response.sendRedirect(request.getContextPath()+"/error?error=" + "Error Message");

Rest easy response status + body

I have following method in my rest service:
#POST
#Path("/create")
#ResponseStatus(HttpStatus.CREATED)
#Consumes(MediaType.WILDCARD)
public String create( .... ) {.... return json;}
so I want to get a response with json in body and status code CREATED.
The problem is: I can't get a response the CREATED status.
The status code is allways OK, so it seems that "#ResponseStatus(HttpStatus.CREATED)" is just ignored...
Can somebody help me with it?
I'm using hibernate 4.1, spring 3.1 and resteasy 2.3
As far as I know, it's not possible to achieve this by annotating the method with #org.springframework.web.bind.annotation.ResponseStatus.
You can return javax.ws.rs.core.Response from your method:
return Response
.status(Response.Status.CREATED)
.entity("ok")
.build();
Or you can have org.jboss.resteasy.spi.HttpResponse injected, and set the status code directly.
There might be more ways of doing this, but I'm only aware of these two.
Working testcase:
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.interception.PostProcessInterceptor;
import org.junit.Assert;
import org.junit.Test;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
public class ResponseTest {
#Path("/")
public static class Service {
#Context HttpResponse response;
#GET
#Path("/1")
public Response createdUsingResponse() throws NotFoundException {
return Response
.status(Response.Status.CREATED)
.entity("ok")
.build();
}
#GET
#Path("/2")
public String created() throws NotFoundException {
response.setStatus(Response.Status.CREATED.getStatusCode());
return "ok";
}
}
public static class Interceptor implements PostProcessInterceptor {
#Context HttpResponse response;
#Override
public void postProcess(ServerResponse response) {
if(this.response.getStatus() != 0){
response.setStatus(this.response.getStatus());
}
}
}
#Test
public void test() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(new Service());
dispatcher
.getProviderFactory()
.getServerPostProcessInterceptorRegistry()
.register(new Interceptor());
{
MockHttpRequest request = MockHttpRequest.get("/1");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assert.assertEquals(201, response.getStatus());
}
{
MockHttpRequest request = MockHttpRequest.get("/2");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assert.assertEquals(201, response.getStatus());
}
}
}

Resources