Spring Boot Validate JSON Mapped via ObjectMapper GET #RequestParam - java

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;
}
}

Related

How to use #Valid annotation in Spring-Data-Rest? [duplicate]

Looking for some help with Spring data rest validation regarding proper handling of validation errors:
I'm so confused with the docs regarding spring-data-rest validation here: http://docs.spring.io/spring-data/rest/docs/current/reference/html/#validation
I am trying to properly deal with validation for a POST call that tries to save a new Company entity
I got this entity:
#Entity
public class Company implements Serializable {
#Id
#GeneratedValue
private Long id;
#NotNull
private String name;
private String address;
private String city;
private String country;
private String email;
private String phoneNumber;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private Set<Owner> owners = new HashSet<>();
public Company() {
super();
}
...
and this RestResource dao
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RestResource;
import com.domain.Company;
#RestResource
public interface CompanyDao extends PagingAndSortingRepository<Company, Long> {
}
POST Request to api/Companies:
{
"address" : "One Microsoft Way",
"city" : "Redmond",
"country" : "USA",
"email" : "info#microsoft.com",
"phoneNumber" : "(425) 703-6214"
}
When I issue a POST with a null name , I get the following rest response with httpcode 500
{"timestamp":1455131008472,"status":500,"error":"Internal Server Error","exception":"javax.validation.ConstraintViolationException","message":"Validation failed for classes [com.domain.Company] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=name, rootBeanClass=class com.domain.Company, messageTemplate='{javax.validation.constraints.NotNull.message}'}\n]","path":"/api/companies/"}
I tried creating the following bean, but it never seems to do anything:
#Component(value="beforeCreateCompanyValidator")
public class BeforeCreateCompanyValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return Company.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object arg0, Errors arg1) {
System.out.println("xxxxxxxx");
}
}
and even if it did work, how would it help me in developing a better error response with a proper http code and understandable json response ?
so confused
using 1.3.2.RELEASE
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
#Mathias
it seems the following is enough for jsr 303 annotations to be checked and for it to auto return a http code of 400 with nice messages (I dont even need BeforeCreateCompanyValidator or BeforeSaveCompanyValidator classes):
#Configuration
public class RestValidationConfiguration extends RepositoryRestConfigurerAdapter{
#Bean
#Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}
400 response:
{
"errors": [{
"entity": "Company",
"message": "may not be null",
"invalidValue": "null",
"property": "name"
}, {
"entity": "Company",
"message": "may not be null",
"invalidValue": "null",
"property": "address"
}]
}
I think your problem is that the bean validation is happening too late - it is done on the JPA level before persist. I found that - unlike spring mvc - spring-data-rest is not doing bean validation when a controller method is invoked. You will need some extra configuration for this.
You want spring-data-rest to validate your bean - this will give you nice error messages responses and a proper http return code.
I configured my validation in spring-data-rest like this:
#Configuration
public class MySpringDataRestValidationConfiguration extends RepositoryRestConfigurerAdapter {
#Bean
#Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
//the bean name starting with beforeCreate will result into registering the validator before insert
public BeforeCreateCompanyValidator beforeCreateCompanyValidator() {
return new BeforeCreateCompanyValidator();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}
When bean validation and/or my custom validator find errors I receive a 400 - bad request with a payload like this:
Status = 400
Error message = null
Headers = {Content-Type=[application/hal+json]}
Content type = application/hal+json
Body = {
"errors" : [ {
"entity" : "siteWithAdminUser",
"message" : "may not be null",
"invalidValue" : "null",
"property" : "adminUser"
} ]
}
The answers by #Mathias and #1977 is enough for regular Spring Data REST calls. However in cases when you need to write custom #RepositoryRestControllers using #RequestBody and #Valid JSR-303 annotations didn't work for me.
So, as an addition to the answer, in case of custom #RepositoryRestControllers with #RequestBody and #Valid annotation I've added the following #ControllerAdvice:
/**
* Workaround class for making JSR-303 annotation validation work for controller method parameters.
* Check the issue DATAREST-593
*/
#ControllerAdvice
public class RequestBodyValidationProcessor extends RequestBodyAdviceAdapter {
private final Validator validator;
public RequestBodyValidationProcessor(#Autowired #Qualifier("mvcValidator") final Validator validator) {
this.validator = validator;
}
#Override
public boolean supports(final MethodParameter methodParameter, final Type targetType, final Class<? extends
HttpMessageConverter<?>> converterType) {
final Annotation[] parameterAnnotations = methodParameter.getParameterAnnotations();
for (final Annotation annotation : parameterAnnotations) {
if (annotation.annotationType().equals(Valid.class)) {
return true;
}
}
return false;
}
#Override
public Object afterBodyRead(final Object body, final HttpInputMessage inputMessage, final MethodParameter
parameter, final Type targetType, final Class<? extends HttpMessageConverter<?>> converterType) {
final Object obj = super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
final BindingResult bindingResult = new BeanPropertyBindingResult(obj, obj.getClass().getCanonicalName());
validator.validate(obj, bindingResult);
if (bindingResult.hasErrors()) {
throw new RuntimeBindException(bindingResult);
}
return obj;
}
}

Using Spring #ModelAttribute with a #RestController

I'm building a REST API that uses a #PathParameter for a parent PK and a #RequestBody for the child form parameters. Next I need to validate the #RequestBody values against regex values stored in a database using the param key from the #RequestBody and the parent pk from the #PathParameter, however I've been unable to figure out a good way to add the #PathParameter pk id to the #RequestBody child object before #Valid is called without using #ModelAttribute.
Using #ModelAttribute, I've been able to add the #PathParameter to the #RequestBody object and then validate the #RequestBody object using #Valid. However I found while using #ModelAttribute, Spring no longer throws the MethodArgumentNotValidException therefore eliminating the ability to use a global exception handler.
I found adding BindingResult to the controller handler followed by throwing a new MethodArgumentNotValidException when errors exist the global exception handler could be triggered.
It's my understanding the ModelAttribute is for Spring MVC and not so much RestController since I'm not using the view, I'm wondering if this is a correct approach or whether there's a better solution. Here's a sample of my code.
HTTP Post
localhost:8072/api/clover/graph_run/2
[
{
"graphKey" : "DATA_DIRECTORY",
"graphValue" : "/data/clover-prod"
},
{
"graphKey" : "DATAOUT_DIR",
"graphValue" : "/data/clover-prod/94l"
},
{
"graphKey" : "DELAY_MS",
"graphValue" : "0"
}
]
RestController
#Slf4j
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/clover")
public class GraphRunController {
final CloverServerService cloverServerService;
#PostMapping("/graph_run/{graphId}")
public String graphRun(#ModelAttribute("graphId") #Valid GraphJobDTO graphJobDTO,
BindingResult bindingResult ) throws MethodArgumentNotValidException {
log.debug("GraphRun {}", graphJobDTO);
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
}
return cloverServerService.runGraph(graphJobDTO);
}
}
ModelAttribute in ControllerAdvise
#Slf4j
#RequiredArgsConstructor
#ControllerAdvice( assignableTypes = {GraphRunController.class})
public class GraphRunControllerAdvise {
final GraphJobRepository graphJobRepository;
#ModelAttribute("graphId")
public GraphJobDTO addGraphId(#PathVariable(value = "graphId") Long graphId,
#RequestBody List<GraphJobPropertyDTO> graphProperties) {
log.debug("ModelAttribute graphId {}", graphId);
//Query database for the graph job and all it's parameters
GraphJob graphJob = graphJobRepository.findById(graphId)
.orElseThrow(() -> new HRINotFoundException("No such job execution."
+ graphId));
GraphJobDTO graphJobDTO = new GraphJobDTO();
graphJobDTO.setGraph(graphJob.getGraph());
graphJobDTO.setGraphProperties(graphProperties);
//Create a map from the database with the key being the GraphKey contained in both the request and the database
Map<String, GraphJobProperty> jobMap = graphJob.getJobProperties().stream().collect(
Collectors.toMap(s -> s.getGraphKey().toUpperCase(), Function.identity()));
graphProperties.forEach(v -> {
String graphKey = v.getGraphKey();
//If the graphKey in the request cannot be found in the database, throw an exception. We will handle the exception
//in th exception handler
if(!jobMap.containsKey(graphKey)) {
throw new HRINotFoundException(String.format("%s is an invalid job parameter.", v.getGraphKey()));
}
//Within the database record is the validation message as well as the regex used to validate the incoming value,
//we set the message and regex in the GraphPropertyDTO object for cross validation later on in the validator.
GraphJobProperty jobProperty = jobMap.get(graphKey);
v.setValidationMessage(jobProperty.getValidationMessage());
v.setValidationRegex(jobProperty.getValidationRegex());
});
//Return the graphJobDTO object for validation
return graphJobDTO;
}
}
Constraint Validator
#Slf4j
public class GraphRegexValidator implements ConstraintValidator<GraphRegex, GraphJobPropertyDTO> {
#Override
public void initialize(final GraphRegex graphRegex) {
}
#Override
public boolean isValid(final GraphJobPropertyDTO dto, final ConstraintValidatorContext context) {
String regex = dto.getValidationRegex();
String value = dto.getGraphValue();
if(regex != null && value != null && !Pattern.matches(regex, value)) {
log.debug("isValid {} - {}", false, dto);
addConstraintViolation(context, getMessage(dto, context));
return false;
}
log.debug("isValid {} - {}", true, dto);
return true;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode("graphKey").addConstraintViolation();
}
private String getMessage(GraphJobPropertyDTO dto, ConstraintValidatorContext context) {
return dto.getValidationMessage() != null ? String.format(dto.getValidationMessage(), dto.getGraphValue()) :
context.getDefaultConstraintMessageTemplate();
}
}
DTOs
#Data
public class GraphJobDTO {
#NotNull
Long graphId;
#NotNull
String graph;
#Valid
List<GraphJobPropertyDTO> graphProperties;
}
#Data
#GraphRegex
public class GraphJobPropertyDTO {
String graphKey;
String graphValue;
String validationMessage;
String validationRegex;
}

How to write custom validation in rest api?

In Spring boot.
I want to do field validation and return an error if the input does not exist in the database.
I am trying to write the custom annotation for multiple input fields.
The controller is as below
#RestController
#Api(description = "The Mailer controller which provides send email functionality")
#Validated
public class SendMailController {
#Autowired
public SendMailService sendemailService;
org.slf4j.Logger logger = LoggerFactory.getLogger(SendMailService.class);
#RequestMapping(method = RequestMethod.POST, value = "/sendMail", consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {"text/xml", "application/json"})
#ResponseBody
#Async(value = "threadPoolTaskExecutor")
#ApiOperation("The main service operation which sends one mail to one or may recipient as per the configurations in the request body")
public Future<SendMailResult> sendMail(#ApiParam("Contains the mail content and configurations to be used for sending mail") #Valid #RequestBody MailMessage message) throws InterruptedException {
SendMailResult results = new SendMailResult();
try {
sendemailService.sendMessages(message);
long txnid = sendemailService.createAudit (message);
results.setTxnid (txnid);
results.setStatus("SUCCESS");
} catch(MessagingException | EmailServiceException e) {
logger.error("Exception while processing sendMail " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
} catch(Exception e) {
logger.error("Something went wrong " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
}
return new AsyncResult<SendMailResult>(results);
}
}
one DTO that is mapped with request
public class MailContext {
#NotNull
private String clientId;
#NotNull
private String consumer;
public int getClientId() {
return Integer.parseInt(clientId);
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String writeValueAsString = mapper.writeValueAsString(this);
return writeValueAsString;
}
}
Request xml
<mailMessage>
<mailContext>
<clientId>10018</clientId>
<consumer>1</consumer>
</mailContext>
</mailMessage>
I want to write a custom annotation to validate client which exists in the database (table client_tbl) if provided in the request.
consumer: is present in database table cunsumer_tbl
if these not present in database send error message else call service method.
Please suggest how to write such custom annotation with the error.
I know another way to validate this.
Inside your controller, you can register a validator.
#InitBinder
public void setup(WebDataBinder webDataBinder) {
webDataBinder.addValidators(dtoValidator);
}
Where dtoValidator is an instance of Spring Bean, for example, which must implements org.springframework.validation.Validator.
So, you just have to implement two methods: supports() and validate(Object target, Errors errors);
Inside supports() method you can do whatever you want to decide whether the object should be validated by this validator or not. (for example, you can create an interface WithClientIdDto and if the tested object isAssignableFrom() this interface you can do this validation. Or you can check your custom annotation is presented on any field using reflection)
For example: (AuthDtoValidator.class)
#Override
public boolean supports(Class<?> clazz) {
return AuthDto.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
final AuthDto dto = (AuthDto) target;
final String phone = dto.getPhone();
if (StringUtils.isEmpty(phone) && StringUtils.isEmpty(dto.getEmail())) {
errors.rejectValue("email", "", "The phone or the email should be defined!");
errors.rejectValue("phone", "", "The phone or the email should be defined!");
}
if (!StringUtils.isEmpty(phone)) {
validatePhone(errors, phone);
}
}
UPDATE:
You can do that.
Create an annotation
for example:
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = ClientIdValidator.class)
#Documented
public #interface ClientId {
String message() default "{some msg}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and implement this validator:
class ClientIdValidator implements ConstraintValidator<ClientId, Long> {
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
//validation logc
}
}
More details you can find here: https://reflectoring.io/bean-validation-with-spring-boot/

#NotNull : validation custom message not displaying

I am using spring data JPA for creating application. In that I am trying to implement server side validation using annotation. I added #NotNull annotation on filed with custom message. I also added #valid with #RequestBody
But problem is that when I am passing nAccountId as null I am not getting custom message i.e. Account id can not be null I am getting "message": "Validation failed for object='accountMaintenanceSave'. Error count: 1",.
Can any one please tell me why I am not getting custom message?
Controller code
#PutMapping("/updateAccountData")
public ResponseEntity<Object> saveData(#Valid #RequestBody AccountMaintenanceSave saveObj){
return accService.saveData(saveObj);
}
AccountMaintenanceSave class
import javax.validation.constraints.NotNull;
public class AccountMaintenanceSave {
#NotNull(message="Account id can not be null")
public Integer nAccountId;
#NotNull
public String sClientAcctId;
#NotNull
public String sAcctDesc;
#NotNull
public String sLocation;
#NotNull
public Integer nDeptId;
#NotNull
public Integer nAccountCPCMappingid;
#NotNull
public Integer nInvestigatorId;
//Getter and Setter
}
RestExceptionHandler class
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I don't know what exactly happen previously and not getting proper message. Now using same code getting result like this with proper message
{
"message": "Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<java.lang.Object> com.spacestudy.controller.AccountController.saveData(com.spacestudy.model.AccountMaintenanceSave), with 1 error(s): [Field error in object 'accountMaintenanceSave' on field 'nAccountId': rejected value [null]; codes [NotNull.accountMaintenanceSave.nAccountId,NotNull.nAccountId,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [accountMaintenanceSave.nAccountId,nAccountId]; arguments []; default message [nAccountId]]; default message [Account id can not be null]] ",
"error": "org.springframework.web.bind.MethodArgumentNotValidException",
"path": "/spacestudy/rockefeller/admin/account/updateAccountData"
}
In message filed can I print only [Account id can not be null]?
Try this.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
// Handle All Field Validation Errors
if(ex instanceof MethodArgumentNotValidException) {
StringBuilder sb = new StringBuilder();
List<FieldError> fieldErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldErrors();
for(FieldError fieldError: fieldErrors){
sb.append(fieldError.getDefaultMessage());
sb.append(";");
}
exceptionMessageObj.setMessage(sb.toString());
}else{
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
}
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
It's not so good to make your only ExceptionHandler to catch Exception.class make it ConstraintViolationException.class
Another approach to the solution:
I would suggest remove the exception handler for this validation fields in the POJO and rather let Spring handle the error response by adding the below property into application.properties. By adding this the message configured in the #notnull or any validation annotation can be captured and showed in the response by default and no explicit handling of this validation case is required.
server.error.include-message=always
server.error.include-binding-errors=always

How to respond with an HTTP 400 error in a Spring MVC #ResponseBody method returning String

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)

Categories

Resources