14

I have defined a global exception handling in my Spring Boot based Rest service:

@ControllerAdvice
public class GlobalExceptionController {

    private final Logger LOG = LoggerFactory.getLogger(getClass());

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Internal application error")
    @ExceptionHandler({ServiceException.class})
    @ResponseBody
    public ServiceException serviceError(ServiceException e) {
        LOG.error("{}: {}", e.getErrorCode(), e.getMessage());
        return e;
    }
}

and a custom ServiceException:

public class ServiceException extends RuntimeException {

    private static final long serialVersionUID = -6502596312985405760L;

    private String errorCode;

    public ServiceException(String message, String errorCode, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    // other constructors, getter and setters omitted
}

so far so good, when an exception is fired the controller works as it should and respond with:

{
   "timestamp": 1413883870237,
   "status": 500,
   "error": "Internal Server Error",
   "exception": "org.example.ServiceException",
   "message": "somthing goes wrong",
   "path": "/index"
}

but the field errorCode isn't shown in the JSON response.

So how can I define a custom exception response in my application.

2 Answers 2

22

Spring Boot uses an implementation of ErrorAttributes to populate the Map that's rendered as JSON. By default, an instance of DefaultErrorAttributes is used. To include your custom errorCode you'll need to provide a custom ErrorAttributes implementation that knows about ServiceException and its error code. This custom implementation should be an a @Bean in your configuration.

One approach would be to sub-class DefaultErrorAttributes:

@Bean
public ErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes() {

        @Override
        public Map<String, Object> getErrorAttributes(
                RequestAttributes requestAttributes,
                boolean includeStackTrace) {
            Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
            Throwable error = getError(requestAttributes);
            if (error instanceof ServiceException) {
                errorAttributes.put("errorCode", ((ServiceException)error).getErrorCode());
            }
            return errorAttributes;
        }

    };
}
2
  • Hi Andy, here If custom exception class extends with Exception or Throwable then error instance would be UnhandledThrowException why it is happening.
    – KSK
    Commented May 29, 2017 at 8:28
  • @KSK It's not clear to me how that's related to this question an answer. I think you'd be better asking a new question with some more details Commented May 29, 2017 at 9:42
4

@Alex You can use annotation @ExceptionHandler(YourExceptionClass.class) to handle the specific exception in specific RestController. I think it's a better way to handle complicated scenarios in business applications. Moreover i will suggest you to use custom exception translator to deal with different type of exceptions. You can consider spring oauth2 exception translator as reference code for exception translator.

Note: Following code is only to understand concept of this solution. It's not production ready code. Feel free to discuss more about it.

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

/**
* @author Harpreet
* @since 16-Aug-17.
*/
@RestController
public class RestTestController {

@RequestMapping(value = "name", method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseObject name(@RequestParam(value="name") String value){
    //your custom logic 
    if (value == null || value.length() < 2) {
        //throwing exception to invoke customExceptionHandler
        throw new NullPointerException("value of request_param:name is invalid");
    }
    ResponseObject responseObject = new ResponseObject();
    responseObject.setMessage(value)
            .setErrorCode(1);
    return responseObject;
}

// to handle null pointer exception
@ExceptionHandler(NullPointerException.class)
public ResponseObject customExceptionHandler
        (NullPointerException e) {
    ResponseObject responseObject = new ResponseObject();
    responseObject.setMessage(e.getMessage())
            .setErrorCode(-1);
    return responseObject;
}

// response data transfer class
static class ResponseObject{
    String message;
    Integer errorCode;

    public String getMessage() {
        return message;
    }

    public ResponseObject setMessage(String message) {
        this.message = message;
        return this;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public ResponseObject setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
        return this;
    }
  }
 }
4
  • I think I like this approach better than redefining ErrorAttributes globally. Although I think it should ideally still utilize the global ErrorAttributes to obtain the default attributes, and then put extra values in. Commented Aug 16, 2017 at 13:54
  • @M. Prokhorov agreed, that will make it more powerful approach Commented Sep 26, 2017 at 7:25
  • @HarpreetSandhu-TheCoder when I try to access this from a java program, it returns the error code, but not the error message. ie, it returns the error code as 404, but the message is not found anywhere in the response object.
    – KayKay
    Commented Oct 27, 2017 at 17:11
  • @kingkari what are you trying to access using java program? can you please elaborate your question. Commented Oct 27, 2017 at 20:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.