3

I want to send a custom JSON response for 400, 401, 405, 403 and 500 errors in spring boot. For these errors spring boot sometimes sends an HTML response, sometimes an empty response body response and sometimes full-fledged response having timestamp, exception, message, etc. I want a single and consistent JSON response for all of these errors, like this

{
statusCode : 405,
message: "HTTP Method not allowed"
}

I have used @ControllerAdvice to send custom responses for a lot of exceptions. The problem I am facing with these 400, 401, 405, 403 and 500 errors is that I don't know which exceptions are thrown when these errors occur so that I can write @ExceptionHandlers for them. Even I don't know if it's possible this way or even if possible then it is the right way to implement this.

Is there any way to implement this in Spring boot?

4 Answers4

1

All the outgoing exceptions are handled by Spring Boot with BasicErrorController.

We can handle/change most of the exceptions message using the controller advice. But, the exceptions which get thrown before reaching the controller, like NotFound, BadRequest etc, are again handle by the BasicErrorController

We can change that as well by overriding the BasicErrorController.

@RestController
@RequestMapping({"${server.error.path:${error.path:/error}}"})
@Slf4j
public class BasicErrorControllerOverride extends AbstractErrorController {

  public BasicErrorControllerOverride(ErrorAttributes errorAttributes) {
    super(errorAttributes);
  }

  @RequestMapping
  public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    HttpStatus status = this.getStatus(request);
    Map<String, Object> errorCustomAttribute = new HashMap<>();
    errorCustomAttribute.put("custome_message", "error");
    errorCustomAttribute.put("custom_status_field", status.name());
    return new ResponseEntity(errorCustomAttribute, status);
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }
}

The error message will now be changed to look like below

{
    "custome_message": "UNAUTHORIZED",
    "custom_status_field": "error"
}
Suraj
  • 737
  • 6
  • 21
  • This solution doesn't work. It throws 404 for every error because `@RequestMapping` has no value. Even after I have added value as error path to it, it still doesn't cater 400 Bad request and other errors. – adarsh srivastava Jun 30 '20 at 07:18
  • Can you share your changes which you tested? the solution is a tested one with spring-boot 2.1.8.RELEASE on my local – Suraj Jun 30 '20 at 07:36
  • have made only one change which is `@RequestMapping("/error")` – adarsh srivastava Jun 30 '20 at 07:58
1

Spring ResponseEntity can be used for this purpose -

return new ResponseEntity<>("Hello World!", HttpStatus.OK);

refer this link; https://www.baeldung.com/spring-response-entity

Well, even if you're not using spring, there are certain simple practices like having all your response classes extend from a base class like this, just an example -

public class StandardResponse{
    private int httpStatus;
  
    //getters and setters
}

public class ProductResponse extends StandardResponse{
    // product specific data
}
Amit Kumar
  • 813
  • 11
  • 13
0

you can make use of @ControllerAdvice and application/problem+json ( zalando/problem is a good java implementation of it)

here is an example: https://www.baeldung.com/problem-spring-web

gaetan224
  • 586
  • 3
  • 13
0

In a similar way as gaetan224 suggested, you can add a controller using @ControllerAdvice to handle exceptions in a custom way:

@ControllerAdvice
@RequestMapping(produces = "application/json")
@ResponseBody
public class RestControllerAdvice {
    
    @Autowired
    Environment env;

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<?> unhandledPath(final NoHandlerFoundException e) {
         
        //This is for 405 METHOD NOT ALLOWED
        //Here you may want to return a ResponseEntity with a custom POJO as body.

    }
    
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<?> methodNotAllowed(final HttpRequestMethodNotSupportedException e) {
        
        //This is for 404 NOT FOUND
        //Here you may want to return a ResponseEntity with a custom POJO as body.

    }

}

Things get a little bit different when handling 403 UNAUTHORIZED in Spring Security. You must write a @Component that implements the AccessDeniedHandler interface. Something like this:

@Component
public class AccessEntryPoint implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest req,
                       HttpServletResponse res,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        
        ObjectMapper mapper = new ObjectMapper();
        res.setContentType("application/json;charset=UTF-8");
        res.setStatus(403);
        res.getWriter().write( /*Your custom object may go here*/ );
    }
}

But this is not enough, you must also set your custom AccessDeniedHandler implementation in your WebSecurityConfigureAdapter implementation using:

@Autowired
AccessEntryPoint accessDeniedHandler;

And appending the following to your configuration method call chain:

.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()

EDIT: I forgot to add that in order for the 404 custom handler to work, you must add this two lines in your application.properties file:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
Santiago Wagner
  • 308
  • 1
  • 9