Skip to content

Instantly share code, notes, and snippets.

@jonikarppinen
Last active June 16, 2021 02:19
Show Gist options
  • Star 55 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save jonikarppinen/662c38fb57a23de61c8b to your computer and use it in GitHub Desktop.
Save jonikarppinen/662c38fb57a23de61c8b to your computer and use it in GitHub Desktop.
Example of replacing Spring Boot "whitelabel" error page with custom error responses (with JSON response body)
package com.company.project.controllers;
import com.company.project.model.api.ErrorJson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* Based on the helpful answer at http://stackoverflow.com/q/25356781/56285,
* with error details in response body added.
*
* @author Joni Karppinen
* @since 20.2.2015
*/
@RestController
public class CustomErrorController implements ErrorController {
private static final String PATH = "/error";
@Value("${debug}")
private boolean debug;
@Autowired
private ErrorAttributes errorAttributes;
@RequestMapping(value = PATH)
ErrorJson error(HttpServletRequest request, HttpServletResponse response) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
// Here we just define response body.
return new ErrorJson(response.getStatus(), getErrorAttributes(request, debug));
}
@Override
public String getErrorPath() {
return PATH;
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
}
@jonikarppinen
Copy link
Author

And the class for defining the error response body; something like this or whatever suits you:

public class ErrorJson {

    public Integer status;
    public String error;
    public String message;
    public String timeStamp;
    public String trace;

    public ErrorJson(int status, Map<String, Object> errorAttributes) {
        this.status = status;
        this.error = (String) errorAttributes.get("error");
        this.message = (String) errorAttributes.get("message");
        this.timeStamp = errorAttributes.get("timestamp").toString();
        this.trace = (String) errorAttributes.get("trace");
    }

}

If you're new to this: Spring implicitly converts objects (that controller methods return) into JSON, using the Jackson library.

Edit: For some needs, using @ExceptionHandler is arguably a handier approach than a custom ErrorController. Here's an example of that: https://gist.github.com/jonikarppinen/6ade2554946df21db0a6.

@zhugw
Copy link

zhugw commented Oct 19, 2015

Hi,
Thanks! But I met a problem, maybe you know it. That is if request method is PUT, when throw exception e.g. valid param failure, it does not call error method in CustomErrorController, but if request method is POST, everything is OK. Do you know why?

@pradhul-dev
Copy link

should we need to provide a custom template page for this? How does this actually works?

@paulc4
Copy link

paulc4 commented Jul 18, 2016

this is a REST controller (see annotation at top of class). So the ErrorJson instance is returned as the HTTP response body. No View or template is used.

@faridrb
Copy link

faridrb commented Jul 25, 2016

How could one do the same with a template in thymeleaf. Can one add the error attributes to a model and pass it to thymeleaf (or jsp)?

@beribener
Copy link

You can also add path to the ErrorJson object, which shows in which URL the exception occurred. Like:
this.path = (String) errorAttributes.get("path");

(populated by spring boot as other variables)

@mohitkira
Copy link

mohitkira commented Feb 6, 2017

I am getting this error :

2017-02-06 13:15:31.805 WARN 12584 --- [ost-startStop-1] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'mappingError' method
org.springframework.http.ResponseEntity<java.lang.String> com.cisco.eloqua.app.MappingError.error(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
to {[/error]}: There is already 'customErrorController' bean method
org.springframework.http.ResponseEntity<java.lang.String> com.cisco.eloqua.app.CustomErrorController.error(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) mapped.
2017-02-06 13:15:31.981 INFO 12584 --- [ost-startStop-1] utoConfigurationReportLoggingInitializer :

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-02-06 13:15:32.002 ERROR 12584 --- [ost-startStop-1] o.s.boot.SpringApplication : Application startup failed

I modified little bit:

@RequestMapping(value = PATH)
ResponseEntity error(HttpServletRequest request, HttpServletResponse response) {
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = org.springframework.http.HttpStatus.valueOf(response.getStatus());
return new ResponseEntity("Cannot Process Your Request Right Now! Please Contact the Administrator for more detail.", responseHeaders,httpStatus);
}

@joshuaherman
Copy link

joshuaherman commented Dec 22, 2017

package io.apptrade.spring.controller;

import io.apptrade.spring.model.ErrorJson;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@log
@RestController
@propertysource("classpath:application.properties") //custom-error-controller.debug place in file and set true/false
@configuration
public class CustomErrorController implements ErrorController{

private static final String PATH = "/error";

@Value("${custom-error-controller.debug}")
private boolean debug;

@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}

@Autowired
private ErrorAttributes errorAttributes;

@RequestMapping(value = PATH)
ResponseEntity<ErrorJson> error(HttpServletRequest request, HttpServletResponse response){
     return ResponseEntity.status(response.getStatus())
         .body(
             new ErrorJson(response.getStatus(), getErrorAttributes(request, debug)
         )
     );
}

@Override
public String getErrorPath(){
    return PATH;
}

private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
    RequestAttributes requestAttributes = new ServletRequestAttributes(request);
    return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}

}

package io.apptrade.spring.model;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.stereotype.Component;

import java.util.Map;

@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY)
@getter(AccessLevel.PUBLIC)
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor
@component
public class ErrorJson{

private Integer status;
private String error;

@JsonIgnore
private String message;
private String timeStamp;
private String trace;

public ErrorJson(int status, Map<String, Object> errorAttributes) {
    this.status = status;
    this.error = (String) errorAttributes.get("error");
    this.message = (String) errorAttributes.get("message");
    this.timeStamp = errorAttributes.get("timestamp").toString();
    this.trace = (String) errorAttributes.get("trace");
}

}
THIS Works!! and has been tested. Produces JSON

@Swis2kk
Copy link

Swis2kk commented Jun 6, 2018

Thank you very much!

@HamedNet
Copy link

Hi
i get this error:

java: cannot access org.springframework.web.reactive.function.server.ServerRequest
class file for org.springframework.web.reactive.function.server.ServerRequest not found

@Vladis466
Copy link

Slightly change the class to use webrequest

`public class CustomErrorController implements ErrorController {

private static final String PATH = "/error";

@Value("${custom-error-controller.debug}")
private boolean debug;

@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}

@Autowired
private ErrorAttributes errorAttributes;

@RequestMapping(value = PATH)
ResponseEntity<ErrorJson> error(WebRequest webRequest, HttpServletResponse response){
    return ResponseEntity.status(response.getStatus())
            .body(
                    new ErrorJson(response.getStatus(), getErrorAttributes(webRequest, debug)
                    )
            );
}

@Override
public String getErrorPath(){
    return PATH;
}

private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    return errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}

}`

@vanduc1102
Copy link

vanduc1102 commented Sep 19, 2018

here is my implementation:
File: application.properties
server.error.whitelabel.enabled=false
File: OverrideErrorWhitePage.java

@Controller
public class OverrideErrorWhitePage implements ErrorController {
  private static final String ERROR_PATH = "/error";

  @ResponseBody
  @ResponseStatus(HttpStatus.NOT_FOUND)
  @RequestMapping(value = ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
  public EcatalogGenericResponse handleError() {
    return ResponseBodyBuilder.notFoundHandler();
  }

  @Override
  public String getErrorPath() {
    return ERROR_PATH;
  }
}

File:App.java

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

File: EcatalogGenericResponse.java

public class EcatalogGenericResponse<T> {
  private String responseCode;
  private String responseMessage;
  private T data;
}

@DPSingh9900
Copy link

DPSingh9900 commented Oct 3, 2018

I want to know how to send error response to custom page in Latest Spring Boot. I tried below mention code but its converts message to JSON format.

@RestController
public class MyCustomErrorHandlerController implements ErrorController {

private static final String PATH = "/error";

@Value("${debug}")
private boolean debug;

@Autowired
private ErrorAttributes errorAttributes;

@RequestMapping(value = PATH)
ErrorJson error(HttpServletRequest request, HttpServletResponse response) {
    // Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring. 
    // Here we just define response body.
	//return new Error(response.getStatus(), getErrorAttributes(request, debug));
    return new ErrorJson(response.getStatus(), getErrorAttributes(request, debug));
}

@Override
public String getErrorPath() {
    return PATH;
}

private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
	WebRequest webRequest = new ServletWebRequest(request);
    return errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}

}

@vijaymohanp
Copy link

I am getting HTTP ERROR 406 code. Anyone has this problem before, at server side everything looks good, but client rejecting the request. I am thinking it is because, browser expecting html and from the server we are trying to send json response?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment