Bean Validation is a good option to validate objects, but how to customize the response of a REST API (using RESTeasy) when a ConstraintViolationException is thrown?
For example:
#POST
#Path("company")
#Consumes("application/json")
public void saveCompany(#Valid Company company) {
...
}
A request with invalid data will return a HTTP 400 status code with the following body:
[PARAMETER]
[saveCompany.arg0.name]
[{company.name.size}]
[a]
It's nice but not enough, I would like to normalize these kind of errors in a JSON document.
How can I customize this behavior?
With JAX-RS can define an ExceptionMapper to handle ConstraintViolationExceptions.
From the ConstraintViolationException, you can get a set of ConstraintViolation, that exposes the constraint violation context, then map the details you need to an abitrary class and return in the response:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
List<ValidationError> errors = exception.getConstraintViolations().stream()
.map(this::toValidationError)
.collect(Collectors.toList());
return Response.status(Response.Status.BAD_REQUEST).entity(errors)
.type(MediaType.APPLICATION_JSON).build();
}
private ValidationError toValidationError(ConstraintViolation constraintViolation) {
ValidationError error = new ValidationError();
error.setPath(constraintViolation.getPropertyPath().toString());
error.setMessage(constraintViolation.getMessage());
return error;
}
}
public class ValidationError {
private String path;
private String message;
// Getters and setters
}
If you use Jackson for JSON parsing, you may want to have a look at this answer, showing how to get the value of the actual JSON property.
Related
when I use the Objects.requireNonNull() to validate the request body, some api return error code 400, others return 500. It should be 400, because the body sent to server side is invalid. I am now confused, do anyone know how to address this. Thanks!
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody Criteria criteria) {
Objects.requireNonNull(criteria, "body is required");
Objects.requireNonNull(criteria.getContentId(), "contentId is required.");
Objects.requireNonNull(criteria.getSchemaVersion(), "schemaVersion is required.");
return findLatestTreeRevisionReference.find(criteria);
}
Objects.requireNonNull() throws a `NullPointerException if the passed parameter is null. Exceptions trigger an Internal Server Error (500).
The status code 400 is not caused by the exception but because controller parameters are not null by default and spring validates this.
As #chrylis -cautiouslyoptimistic- pointed out in the comments, you can use #Valid:
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody #Valid Criteria criteria) {
return findLatestTreeRevisionReference.find(criteria);
}
For this to work, Criteria needs to use proper annotations:
public class Criteria{
#NotNull
private String contentId;
#NotNull
private String schemaVersion;
//Getters/Setters
}
You can also create a custom exception and/or exception handler in your controller as described here if #Valid is not viable.
For example, you could catch every NullPointerException in your controller and send back a 400 Bad Request status:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NullPointerException.class)
public void handleNPE() {
//Done by #ResponseStatus
}
However, NullPointerExceptions might occur because of different reasons than the user sending an invalid request. In order to bypass that issue, you can create your own exception that translates to the error 400:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Request body invalid")
public class InvalidBodyException extends RuntimeException {
//No need for additional logic
}
You can then make your own method that does the null check and use that instead of Objects.requireNonNull:
public void requireNonNull(Object o){
if(o==null){
throw new InvalidBodyException();
}
}
I am new to RESTful web services in general, and am learning the Spring implementation of web services.
I am particularly interested in learning how to properly use ResponseEntity return types for most of my use cases.
I have one endpoint:
/myapp/user/{id}
This endpoint supports a GET request, and will return a JSON formatted string of the User object whose ID is {id}. I plan to annotate the controller method as producing JSON.
In the case that a user with ID {id} exists, I set a status of 200, and set the JSON string of the user in the body.
In the event that no user exists with that ID, I was going to return some flavor of a 400 error, but I am not sure what to set in the body of the ResponseEntity. Since I annotate the endpoint method as producing JSON, should I come up with a generic POJO that represents an error, and return that as JSON?
You don´t need to use a generic Pojo, 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);
}
}
In the event that no user exists with that ID, I was going to return
some flavor of a 400 error, but I am not sure what to set in the body
of the ResponseEntity. Since I annotate the endpoint method as
producing JSON, should I come up with a generic POJO that represents
an error, and return that as JSON?
This is definitely a possible solution, if you want to add e.g. a more specific reason why the request failed or if you want to add a specific I18N message or just want to generify your API to provide some abstract structure.
I myself prefer the solution #Herr Derb suggested, if there is nothing to return, don't return anything. The returned data may be completely unnecessary/unused and the receiver may just discard it if the return code is anything else than 2XX.
This may be related:
http://www.bbenson.co/post/spring-validations-with-examples/
The author describes how to validate incoming models and builds a generic error response. Maybe this is something you want to do...
I have a resource:
#GET
#Path("/print-order")
#Produces("application/pdf")
public byte[] printOrder(#QueryParam("id") Long orderId) {
return ...;
}
...which can throw an error that is relevant to the user and must be displayed as a HTML page. So I implemented an ExceptionMapper but I don't know how to get the value of #Produces("application/pdf") annotation of the called resource.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
#Override
public Response toResponse(CustomException exception) {
if (contentType = "application/pdf")
... html respone
else
... entity response
}
}
I'm using JAX-RS 1.x (jsr311) with Jersy 1.12 implementation but would love to have implementation independent solution.
You can inject different context objects into the ExceptionMapper to get more info on the request it handles. It's convenient to determine what content type the client expects based on HTTP's Accept header (learn more here).
Below is the example on how you can make ExceptionMapper to respond with different formats based on Accept header specified (or not specified) by your APIs client.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
// Inject headers of the request being processed
#Context
private HttpHeaders headers;
// or even all the request details
#Context
private HttpServletRequest request;
#Override
public Response toResponse(CustomException exception) {
List<MediaType> acceptedTypes = headers.getAcceptableMediaTypes();
if (acceptedTypes.contains(MediaType.APPLICATION_JSON)) {
// respond with entity
} else {
// respond with HTML
}
}
}
You initial idea can be implemented, though. You can inject HttpServletRequest in your resource class and use setAttribute() method to store application/pdf string within the context of current request. It can be later obtained in ExceptionMapper using getAttribute() method. But I wouldn't recommend to do so unless absolutely necessary. It introduces not so obvious dependencies between components of your code.
I am trying to return an object as XML in spring, exactly like this guide: http://spring.io/guides/gs/rest-service/
Except that I want the object to return as xml instead of JSON.
Anyone know how I can do that?
Does Spring have any dependancies that can do this as easily for XML? Or, do I need to use a marshaller and then return the xml file some other way?
Spring supports JSON by default, but to support XML as well, do these steps -
In the class you plan to return as response, add xml annotations. for e.g.
#XmlRootElement(name = "response")
#XmlAccessorType(XmlAccessType.FIELD) => this is important, don't miss it.
public class Response {
#XmlElement
private Long status;
#XmlElement
private String error;
public Long getStatus() {
return status;
}
public void setStatus(Long status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
Add produces and consumes to your #RequestMapping on the restful method like below, this helps in making sure what kind of responses and request you support, if you only want response as xml, only put produces = "application/xml".
#RequestMapping(value = "/api", method = RequestMethod.POST, consumes = {"application/xml", "application/json"}, produces = {"application/xml", "application/json"})
public
Then, make sure you return the response object from your method call like below, you can add #ResponseBody just before return type but in my experience, my app worked fine without it.
public Response produceMessage(#PathVariable String topic, #RequestBody String message) {
return new Response();
}
Now, if you are supporting multiple produces types, then based on what client sent as the Accept in the HTTP request header, the spring restful service will return that type of response. If you only want to support xml, then only produce 'application/xml' and the response will always be xml.
If you use JAXB annotations in your bean to define #XmlRootElement and #XmlElement then it should marshall it xml. Spring will marshall the bean to xml when it sees:
Object annotated with JAXB
JAXB library existed in classpath
“mvc:annotation-driven” is enabled
Return method annotated with #ResponseBody
Follow this sample to know more:
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-xml-example/
I have a Jersey service that's accepting an object as XML via a POST method. There are some errors being raised during the marshalling due to invalid data as expected (fields too long, etc). How can I capture and return these errors gracefully instead of getting the generic "The request sent by the client was syntactically incorrect" error.
Clarification: The bodies of the REST calls are like this:
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Path("/users")
#POST
public Response createUser(#Context SecurityContext sc, User user) {
// do some stuff
}
Where the user object is sent as the body of the call. The errors are being raised in the sets of the User object.
Oh, first you will need to change the argument of your method to take either a String or a Map. Then you can control the marshaling something like so:
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Path("/users")
#POST
public Response createUser(#Context SecurityContext sc, String json ) {
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue( json, User.class );
Then you can put a try..catch around this to catch the validation errors. As for returning this to the client I like to create a Response of BAD_REQUEST and then create an object for the entity of the request. I create one that looks very much like a ConstrainViolationExcpetion. It basically has a one-line message description then a collection of Objects that have fields for "field" and "detail". Then I let it be returned as JSON or XML to the client. Here's an example output in JSON:
{"description":"Validation Failed",
"errors":[
{"field":"emailAddress","message":"not a well-formed email address"}
{"field":"phoneNumber","message":"The phone number supplied was null."},
{"field":"password","message":"may not be null"}]}
Here's a quick and dirty example that return a reponse entity based on a basic ConstrainViolationException but I think you can see how to easily add "field" and "message" elements to instances of this class.
public class RestValidationErrorEntity {
public static Response createResponseOrThrow( ConstraintViolationException e ) {
return Response
.status( Response.Status.BAD_REQUEST )
.entity( new RestValidationErrorEntity( e ) )
.build();
}
public String description = "Validation Failed";
public List<Detail> errors = new ArrayList<>();
public RestValidationErrorEntity( ConstraintViolationException e ) {
for ( ConstraintViolation<?> violation : e.getConstraintViolations() ) {
errors.add(
new Detail(
violation.getPropertyPath().toString(), violation.getMessage()
) );
}
}
public static class Detail {
public String field;
public String message;
Detail( String field, String message ) {
this.message = message;
this.field = field;
}
}
}