I'm working on a Rest API. Currently, I'm trying to handle some wrong client input. So far so good, I have it working with java validation.
There is an specific case where I may have an odd client input for dates. Here's a mvce:
public class Input {
#NotNull(message = "Usage to should have a valid date")
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime someDateTime;
//getters and setters
}
public class Output {
//content not relevant for the case...
}
#RestController
public class FooController {
#PostMapping(path="/url", consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Output> foo(#Valid #ResponseBody Input input) {
//implementation details...
}
}
When testing it, this input works ok:
{
"someDateTime": "2019-01-01 00:00:00"
}
And this doesn't (time is missing):
{
"someDateTime": "2019-01-01"
}
For the latest input sample, I get an HttpMessageNotReadableException (from Spring).
I was trying to solve this by adding a method in a global exception handler:
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> customSpringInputHandler(
HttpMessageNotReadableException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(CLIENT_INPUT_EXCEPTION, e.getMessage()));
}
}
With this, now I get a more detailed exception message. Sadly, this message is too verbose to return to the clients of this API because it depicts some of the internal design of the application.
I can see this HttpMessageNotReadableException wraps an InvalidFormatException (from Jackson) that's better for me to use. Thing is, handling specific cases of the case of HttpMessageNotReadableException in a single method seems kinda fishy. I was thinking on create a specific exception handler for InvalidFormatException and then delegate that execution from this method. Now I want to know how Spring can provide me those handlers and do the proper delegation.
I had something built in JAX-RS in the past, it was like this:
#SuppressWarnings("unchecked")
protected <T extends Throwable> Response callChainedMapper(T wrappedException, Exception baseException) {
Response response;
ExceptionMapper<T> mapper = (ExceptionMapper<T>) providers.getExceptionMapper(wrappedException.getClass());
//no mapper could be found, so treat it as a generic exception
if (mapper == null) {
ExceptionMapper<Exception> generalExceptionMapper = (ExceptionMapper<Exception>) providers
.getExceptionMapper(Exception.class);
response = generalExceptionMapper.toResponse(baseException);
} else {
response = mapper.toResponse(wrappedException);
}
return response;
}
Can I do something similar in Spring? Or maybe is there another alternative ?
Not aware Spring provides specific feature for such delegation. But you can simply
do it by yourself by the following pattern. The ResponseEntityExceptionHandler provided by Spring also use this kind of pattern.
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({
HttpMessageNotReadableException.class ,
InvalidFormatException.class})
public ResponseEntity<ErrorResponse> customSpringInputHandler(Exception ex) {
if(ex instanceof HttpMessageNotReadableException){
return handle((HttpMessageNotReadableException) ex);
}else if(ex instance of InvalidFormatException) {
return handle((InvalidFormatException) ex);
}else{
throw ex;
}
}
private ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException ex){
//get the wrapped InvalidFormatException and call handle(invalidFormatException)
}
private ResponseEntity<ErrorResponse> handle(InvalidFormatException ex){
}
}
I am trying to handle all Types of exceptions using #ExceptionHandler(Exception.class). But it's not handling all types of exception.
When I am trying to access wrong HTTP method from postman/ browser I am not getting any response blank page is coming.
Can please any one tell me why I am not getting any response or tell me if I am doing something wrong in my code?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionMessage> handleAllExceptionMethod(Exception ex,WebRequest requset,HttpServletResponse res) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setStatus(res.getStatus());
exceptionMessageObj.setError(ex.getLocalizedMessage());
exceptionMessageObj.setException(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
return new ResponseEntity<ExceptionMessage>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
Either override ResponseEntityExceptionHandler#handleExceptionInternal()or don't extend ResponseEntityExceptionHandler.
#Order(Ordered.HIGHEST_PRECEDENCE) on a #ControllerAdvice should work before ResponseEntityExceptionHandler is invoked as per this answer which suggests that Spring Framework 4.3.7 is needed.
This will handle the exceptions raised from within the controller method.
If you send a request for which there is no mapping the controller method will not be invoked at all thus the #ExceptionHandler will be obsolete in that case.
Maybe this article on creating custom handlers may help: article
Using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
What's the simplest approach to validating a complex JSON object being passed into a GET REST contoller in spring boot that I am mapping with com.fasterxml.jackson.databind.ObjectMapper?
Here is the controller:
#RestController
#RequestMapping("/products")
public class ProductsController {
#GetMapping
public ProductResponse getProducts(
#RequestParam(value = "params") String requestItem
) throws IOException {
final ProductRequest productRequest =
new ObjectMapper()
.readValue(requestItem, ProductRequest.class);
return productRetriever.getProductEarliestAvailabilities(productRequest);
}}
DTO request object I want to validate:
public class ProductRequest {
private String productId;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}}
I was thinking of using annotations on the request DTO however when I do so, they are not triggering any type of exceptions, i.e. #NotNull. I've tried various combinations of using #Validated at the controller as well as #Valid in the #RequestParam and nothing is causing the validations to trigger.
In my point of view, Hibernate Bean Validator is probably one of the most convenient methods to validate the annotated fields of a bean anytime and anywhere. It's like setup and forget
Setup the Hibernate Bean Validator
Configure how the validation should be done
Trigger the validator on a bean anywhere
I followed the instructions in the documentation given here
Setup dependencies
I use Gradle so, I am going to add the required dependencies as shown below
// Hibernate Bean validator
compile('org.hibernate:hibernate-validator:5.2.4.Final')
Create a generic bean valdiator
I setup a bean validator interface as described in the documentation and then use this to validate everything that is annotated
public interface CustomBeanValidator {
/**
* Validate all annotated fields of a DTO object and collect all the validation and then throw them all at once.
*
* #param object
*/
public <T> void validateFields(T object);
}
Implement the above interface as follow
#Component
public class CustomBeanValidatorImpl implements CustomBeanValidator {
ValidatorFactory valdiatorFactory = null;
public CustomBeanValidatorImpl() {
valdiatorFactory = Validation.buildDefaultValidatorFactory();
}
#Override
public <T> void validateFields(T object) throws ValidationsFatalException {
Validator validator = valdiatorFactory.getValidator();
Set<ConstraintViolation<T>> failedValidations = validator.validate(object);
if (!failedValidations.isEmpty()) {
List<String> allErrors = failedValidations.stream().map(failure -> failure.getMessage())
.collect(Collectors.toList());
throw new ValidationsFatalException("Validation failure; Invalid request.", allErrors);
}
}
}
The Exception class
The ValidationsFatalException I used above is a custom exception class that extends RuntimeException. As you can see I am passing a message and a list of violations in case the DTO has more than one validation error.
public class ValidationsFatalException extends RuntimeException {
private String message;
private Throwable cause;
private List<String> details;
public ValidationsFatalException(String message, Throwable cause) {
super(message, cause);
}
public ValidationsFatalException(String message, Throwable cause, List<String> details) {
super(message, cause);
this.details = details;
}
public List<String> getDetails() {
return details;
}
}
Simulation of your scenario
In order to test whether this is working or not, I literally used your code to test and here is what I did
Create an endpoint as shown above
Autowire the CustomBeanValidator and trigger it's validateFields method passing the productRequest into it as shown below
Create a ProductRequest class as shown above
I annotated the productId with #NotNull and #Length(min=5, max=10)
I used Postman to make a GET request with a params having a value that is url-encoded json body
Assuming that the CustomBeanValidator is autowired in the controller, trigger the validation as follow after constructing the productRequest object.
beanValidator.validateFields(productRequest);
The above will throw exception if any violations based on annotations used.
How is the exception handled by exception controller?
As mentioned in the title, I use ExceptionController in order to handle the exceptions in my application.
Here is how the skeleton of my exception handler where the ValidationsFatalException maps to and then I update the message and set my desired status code based on exception type and return a custom object (i.e. the json you see below)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({SomeOtherException.class, ValidationsFatalException.class})
public #ResponseBody Object handleBadRequestExpection(HttpServletRequest req, Exception ex) {
if(ex instanceof CustomBadRequestException)
return new CustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage());
else
return new DetailedCustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage(),((ValidationsFatalException) ex).getDetails());
}
Test 1
Raw params = {"productId":"abc123"}
Url encoded parmas = %7B%22productId%22%3A%22abc123%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
Result: All good.
Test 2
Raw params = {"productId":"ab"}
Url encoded parmas = %7B%22productId%22%3A%22ab%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22ab%22%7D
Result:
{
"statusCode": 400,
"status": "BAD_REQUEST",
"message": "Validation failure; Invalid request.",
"details": [
"length must be between 5 and 10"
]
}
You can expand the Validator implementation to provide a mapping of field vs message error message.
Do you mean something like this ?
#RequestMapping("/products")
public ResponseEntity getProducts(
#RequestParam(value = "params") String requestItem) throws IOException {
ProductRequest request = new ObjectMapper().
readValue(requestItem, ProductRequest.class);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ProductRequest>> violations
= validator.validate(request);
if (!violations.isEmpty()) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
public class ProductRequest {
#NotNull
#Size(min = 3)
private String id;
public String getId() {
return id;
}
public String setId( String id) {
return this.id = id;
}
}
I have a Request Mapping -
#RequestMapping("/fetchErrorMessages")
public #ResponseBody int fetchErrorMessages(#RequestParam("startTime") String startTime,#RequestParam("endTime") String endTime) throws Exception
{
if(SanityChecker.checkDateSanity(startTime)&&SanityChecker.checkDateSanity(endTime))
{
return 0;
}
else
{
throw new NotFoundException("Datetime is invalid");
}
}
If the startTime and endTime are invalid, I want to throw a 500 error but return the exception string in JSON. However, I get a HTML Page instead saying
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Dec 20 10:49:37 IST 2017
There was an unexpected error (type=Internal Server Error, status=500).
Datetime is invalid
I instead wanted to return 500 with a JSON
{"error":"Date time format is invalid"}
How do I go about this?
Suppose you have a custom Exception class NotFoundException and its implementations something like this:
public class NotFoundException extends Exception {
private int errorCode;
private String errorMessage;
public NotFoundException(Throwable throwable) {
super(throwable);
}
public NotFoundException(String msg, Throwable throwable) {
super(msg, throwable);
}
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException(String message, int errorCode) {
super();
this.errorCode = errorCode;
this.errorMessage = message;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
#Override
public String toString() {
return this.errorCode + " : " + this.getErrorMessage();
}
}
Now you want to throw some exception from controller. If you throw a exception then you must catch it from a standard Error Handler class, say for example in spring they provide #ControllerAdvice annotation to apply to make a class Standard Error Handler. When it is applied to a class then this spring component (I mean the class you annotated) can catch any exception thrown from controller. But We need to map exception class with proper method. So we defined a method with your exception NotFoundException handler something like below.
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public Object processValidationError(NotFoundException ex) {
String result = ex.getErrorMessage();
System.out.println("###########"+result);
return ex;
}
}
You want to sent http status to internal server error(500), so here we used #ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR). Since you used Spring-boot so you do not need to make a json string except a simple annotation #ResponseBody can do that for you automagically.
Create a custom exception.
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = -7806029002430564887L;
private String message;
public SecurityException() {
}
public SecurityException(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Create a custom response entity.
public class SecurityResponse {
private String error;
public SecurityResponse() {
}
public SecurityResponse(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
Create a ControllerAdvice with ExceptionHandler for custom exception, it will handle the custom exception, populate and return the custom response as below.
#ControllerAdvice
public class SecurityControllerAdvice {
#ExceptionHandler(SecurityException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public SecurityResponse handleSecurityException(SecurityException se) {
SecurityResponse response = new SecurityResponse(se.getMessage());
return response;
}
}
Throw the custom exception based on your condition.
throw new SecurityException("Date time format is invalid");
Now run and test you app. E.G. :
you can create NotFoundException class with #ResponseStatus annotation like below:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class NotFoundException extends RuntimeException {
public NotFoundException() {
}
public NotFoundException(String message) {
super(message);
}
}
Javax has a interface name as ExceptionMapper. Please refer the below code snippet, For every RuntimeException in your application it will map it to a Json Response entity.
public class RuntimeExceptionMapper implements ExceptionMapper <RuntimeException> {
#Override
public Response toResponse(RuntimeException exception) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage(exception.getMessage);
if (exception== null) {
logger.error("Exception Details Not found");
} else {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(errorResponse )
.type(MediaType.APPLICATION_JSON)
.header("trace-id", "1234").build();
}
}
}
This is how I did it in my application:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class ExceptionHandlingControllerAdvice {
#ExceptionHandler(ExecutionRestrictionViolationException.class)
public ResponseEntity<String> handleExecutionRestrictionViolationException(ExecutionRestrictionViolationException ex) {
return response("Invalid Query", ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
}
private static String createJson(String message, String reason) {
return "{\"error\" : \"" + message + "\"," +
"\"reason\" : \"" + reason + "\"}";
}
private static ResponseEntity<String> response(String message,
String reason,
HttpStatus httpStatus) {
String json = createJson(message, reason);
return new ResponseEntity<>(json, httpStatus);
}
}
Explanation:
You create a controller Advice, mark it with a special annotation and define just like any other bean (in my case it was a java configuration, but it doesn't really matter)
For each Exception you would like to handle like this - define a handler that will generate a response in a format you want
There is a static method createJson - you can use a different way, it also doesn't matter really.
Now this is only one way to work (its available in more recent spring boot versions) - but there are others:
All the methods I'm aware of (and even more) are listed here.
Spring provides a few ways to do this, some more sensible than others depending on your situation.
(Great tutorial here on several options. https://www.baeldung.com/spring-exceptions-json)
My favorite is this one because I want to send back a proper error message and an appropriate http response without creating a superclass or creating helper methods in a utility class or copying boilerplate everywhere.
If you want to inform the caller that the event caused an error (and in proper JSON), use Spring's ResponseStatusException. It gives you access to the httpReponse object so you can also send back a response other than 'ok'.
It wants an exception as one of it's parameters. For one of my scenarios I wanted to inform the caller that they were trying to register a user that already existed. Typically, looking up a user isn't supposed to throw an exception but in this case I created my own exception and I throw it back to the caller in a ResponseStatusException like so:
#PostMapping("/register")
public ResponseEntity register(#RequestBody AccountUserDto user) {
UserDetails userExists = userDetailsService.loadUserByEmail(user.getEmail());
if (userExists != null) {
UserExistsException exc = new UserExistsException("Error: Email address " + user.getEmail() + " is already in use.");
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "User Exists", exc);
}
....(fall through and create user)
I'm using Spring MVC for a simple JSON API, with #ResponseBody based approach like the following. (I already have a service layer producing JSON directly.)
#RequestMapping(value = "/matches/{matchId}", produces = "application/json")
#ResponseBody
public String match(#PathVariable String matchId) {
String json = matchService.getMatchJson(matchId);
if (json == null) {
// TODO: how to respond with e.g. 400 "bad request"?
}
return json;
}
In the given scenario, what is the simplest, cleanest way to respond with a HTTP 400 error?
I did come across approaches like:
return new ResponseEntity(HttpStatus.BAD_REQUEST);
...but I can't use it here since my method's return type is String, not ResponseEntity.
Change your return type to ResponseEntity<>, and then you can use the below for 400:
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
And for a correct request:
return new ResponseEntity<>(json,HttpStatus.OK);
After Spring 4.1 there are helper methods in ResponseEntity which could be used as:
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
and
return ResponseEntity.ok(json);
Something like this should work, but I'm not sure whether or not there is a simpler way:
#RequestMapping(value = "/matches/{matchId}", produces = "application/json")
#ResponseBody
public String match(#PathVariable String matchId, #RequestBody String body,
HttpServletRequest request, HttpServletResponse response) {
String json = matchService.getMatchJson(matchId);
if (json == null) {
response.setStatus( HttpServletResponse.SC_BAD_REQUEST );
}
return json;
}
It is not necessarily the most compact way of doing this, but quite clean in my opinion:
if(json == null) {
throw new BadThingException();
}
...
#ExceptionHandler(BadThingException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public #ResponseBody MyError handleException(BadThingException e) {
return new MyError("That doesn’t work");
}
You can use #ResponseBody in the exception handler method if using Spring 3.1+, otherwise use a ModelAndView or something.
#ResponseBody does not work with #ExceptionHandler [SPR-6902] #11567
I would change the implementation slightly:
First, I create a UnknownMatchException:
#ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
public UnknownMatchException(String matchId) {
super("Unknown match: " + matchId);
}
}
Note the use of #ResponseStatus, which will be recognized by Spring's ResponseStatusExceptionResolver. If the exception is thrown, it will create a response with the corresponding response status. (I also took the liberty of changing the status code to 404 - Not Found which I find more appropriate for this use case, but you can stick to HttpStatus.BAD_REQUEST if you like.)
Next, I would change the MatchService to have the following signature:
interface MatchService {
public Match findMatch(String matchId);
}
Finally, I would update the controller and delegate to Spring's MappingJackson2HttpMessageConverter to handle the JSON serialization automatically (it is added by default if you add Jackson to the classpath and add either #EnableWebMvc or <mvc:annotation-driven /> to your config. See the reference documentation):
#RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Match match(#PathVariable String matchId) {
// Throws an UnknownMatchException if the matchId is not known
return matchService.findMatch(matchId);
}
Note, it is very common to separate the domain objects from the view objects or DTO objects. This can easily be achieved by adding a small DTO factory that returns the serializable JSON object:
#RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public MatchDTO match(#PathVariable String matchId) {
Match match = matchService.findMatch(matchId);
return MatchDtoFactory.createDTO(match);
}
Here's a different approach. Create a custom Exception annotated with #ResponseStatus, like the following one.
#ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {
public NotFoundException() {
}
}
And throw it when needed.
#RequestMapping(value = "/matches/{matchId}", produces = "application/json")
#ResponseBody
public String match(#PathVariable String matchId) {
String json = matchService.getMatchJson(matchId);
if (json == null) {
throw new NotFoundException();
}
return json;
}
The easiest way is to throw a ResponseStatusException:
#RequestMapping(value = "/matches/{matchId}", produces = "application/json")
#ResponseBody
public String match(#PathVariable String matchId, #RequestBody String body) {
String json = matchService.getMatchJson(matchId);
if (json == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return json;
}
As mentioned in some answers, there is the ability to create an exception class for each HTTP status that you want to return. I don't like the idea of having to create a class per status for each project. Here is what I came up with instead.
Create a generic exception that accepts an HTTP status
Create an Controller Advice exception handler
Let's get to the code
package com.javaninja.cam.exception;
import org.springframework.http.HttpStatus;
/**
* The exception used to return a status and a message to the calling system.
* #author norrisshelton
*/
#SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {
private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
/**
* Gets the HTTP status code to be returned to the calling system.
* #return http status code. Defaults to HttpStatus.INTERNAL_SERVER_ERROR (500).
* #see HttpStatus
*/
public HttpStatus getHttpStatus() {
return httpStatus;
}
/**
* Constructs a new runtime exception with the specified HttpStatus code and detail message.
* The cause is not initialized, and may subsequently be initialized by a call to {#link #initCause}.
* #param httpStatus the http status. The detail message is saved for later retrieval by the {#link
* #getHttpStatus()} method.
* #param message the detail message. The detail message is saved for later retrieval by the {#link
* #getMessage()} method.
* #see HttpStatus
*/
public ResourceException(HttpStatus httpStatus, String message) {
super(message);
this.httpStatus = httpStatus;
}
}
Then I create a controller advice class
package com.javaninja.cam.spring;
import com.javaninja.cam.exception.ResourceException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Exception handler advice class for all SpringMVC controllers.
* #author norrisshelton
* #see org.springframework.web.bind.annotation.ControllerAdvice
*/
#org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {
/**
* Handles ResourceExceptions for the SpringMVC controllers.
* #param e SpringMVC controller exception.
* #return http response entity
* #see ExceptionHandler
*/
#ExceptionHandler(ResourceException.class)
public ResponseEntity handleException(ResourceException e) {
return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
}
}
To use it
throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");
http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/
I’m using this in my Spring Boot application:
#RequestMapping(value = "/matches/{matchId}", produces = "application/json")
#ResponseBody
public ResponseEntity<?> match(#PathVariable String matchId, #RequestBody String body,
HttpServletRequest request, HttpServletResponse response) {
Product p;
try {
p = service.getProduct(request.getProductId());
} catch(Exception ex) {
return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(p, HttpStatus.OK);
}
With Spring Boot, I'm not entirely sure why this was necessary (I got the /error fallback even though #ResponseBody was defined on an #ExceptionHandler), but the following in itself did not work:
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
log.error("Illegal arguments received.", e);
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.code = 400;
errorMessage.message = e.getMessage();
return errorMessage;
}
It still threw an exception, apparently because no producible media types were defined as a request attribute:
// AbstractMessageConverterMethodProcessor
#SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Class<?> valueType = getReturnValueType(value, returnType);
Type declaredType = getGenericType(returnType);
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (value != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType); // <-- throws
}
// ....
#SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<MediaType>(mediaTypes);
So I added them.
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
Set<MediaType> mediaTypes = new HashSet<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
httpServletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
log.error("Illegal arguments received.", e);
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.code = 400;
errorMessage.message = e.getMessage();
return errorMessage;
}
And this got me through to have a "supported compatible media type", but then it still didn't work, because my ErrorMessage was faulty:
public class ErrorMessage {
int code;
String message;
}
JacksonMapper did not handle it as "convertable", so I had to add getters/setters, and I also added #JsonProperty annotation
public class ErrorMessage {
#JsonProperty("code")
private int code;
#JsonProperty("message")
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Then I received my message as intended
{"code":400,"message":"An \"url\" parameter must be defined."}
Another approach is to use #ExceptionHandler with #ControllerAdvice to centralize all your handlers in the same class. If not, you must put the handler methods in every controller you want to manage an exception for.
Your handler class:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyBadRequestException.class)
public ResponseEntity<MyError> handleException(MyBadRequestException e) {
return ResponseEntity
.badRequest()
.body(new MyError(HttpStatus.BAD_REQUEST, e.getDescription()));
}
}
Your custom exception:
public class MyBadRequestException extends RuntimeException {
private String description;
public MyBadRequestException(String description) {
this.description = description;
}
public String getDescription() {
return this.description;
}
}
Now you can throw exceptions from any of your controllers, and you can define other handlers inside you advice class.
The simplest and cleanest way to handle exceptions in your controller without having to explicitly return ResponseEntity is to just add #ExceptionHandler methods.
Example snippet using Spring Boot 2.0.3.RELEASE:
// Prefer static import of HttpStatus constants as it's cleaner IMHO
// Handle with no content returned
#ExceptionHandler(IllegalArgumentException.class)
#ResponseStatus(BAD_REQUEST)
void onIllegalArgumentException() {}
// Return 404 when JdbcTemplate does not return a single row
#ExceptionHandler(IncorrectResultSizeDataAccessException.class)
#ResponseStatus(NOT_FOUND)
void onIncorrectResultSizeDataAccessException() {}
// Catch all handler with the exception as content
#ExceptionHandler(Exception.class)
#ResponseStatus(I_AM_A_TEAPOT)
#ResponseBody Exception onException(Exception e) {
return e;
}
As an aside:
If in all contexts/usages, matchService.getMatchJson(matchId) == null is invalid, then my suggestion would be to have getMatchJson throw an exception, e.g., IllegalArgumentException instead of returning null and let it bubble up to the controller's #ExceptionHandler.
If null is used to test other conditions then I would have a specific method, e.g., matchService.hasMatchJson(matchId). In general, I avoid null if possible in order to avoid an unexpected NullPointerException.
You also could just throw new HttpMessageNotReadableException("error description") to benefit from Spring's default error handling.
However, just as is the case with those default errors, no response body will be set.
I find these useful when rejecting requests that could reasonably only have been handcrafted, potentially indicating a malevolent intent, since they obscure the fact that the request was rejected based on a deeper, custom validation and its criteria.
Use a custom response with the status code.
Like this:
class Response<T>(
val timestamp: String = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
.withZone(ZoneOffset.UTC)
.format(Instant.now()),
val code: Int = ResultCode.SUCCESS.code,
val message: String? = ResultCode.SUCCESS.message,
val status: HttpStatus = HttpStatus.OK,
val error: String? = "",
val token: String? = null,
val data: T? = null
) : : ResponseEntity<Response.CustomResponseBody>(status) {
data class CustomResponseBody(
val timestamp: String = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
.withZone(ZoneOffset.UTC)
.format(Instant.now()),
val code: Int = ResultCode.SUCCESS.code,
val message: String? = ResultCode.SUCCESS.message,
val error: String? = "",
val token: String? = null,
val data: Any? = null
)
override fun getBody(): CustomResponseBody? = CustomResponseBody(timestamp, code, message, error, token, data)