I am validating java beans using spring-boot-starter-validation.
Validation on the controller is working fine,
I want to know whether can we validate the normal methods of a class using #Valid annotation? I have tried it but not working.
My working solution on the controller
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#Valid #RequestBody InternalMsg internalMsg) {
return ResponseEntity.ok("Valid Message");
}
I want to move the validation to a class method so that when I hit the RestAPI, the validation errors are captured in a new method.
Let's say the method is validateMsg of class MsgValidator and I am calling this method inside controller code
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#RequestBody InternalMsg internalMsg) { // No #Valid here
MsgValidator msgValidator = new MsgValidator();
Boolean isValid = msgValidator.validateMsg(internalMsg);
// some other processings
return ResponseEntity.ok("Valid Message");
}
public class MsgValidator{
public boolean validateMsg(#Valid InteropMsg interopMsg){
return true;
}
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Ack> handleValidationExceptions(MethodArgumentNotValidException ex) {
StringBuilder errorMessages = new StringBuilder("");
ex.getBindingResult().getAllErrors().forEach((error) -> {
errorMessages.append(error.getDefaultMessage()).append(";");
});
log.error("Validation errors : "+errorMessages.toString());
return ResponseEntity.ok().body(ack);
}
}
public class InternalMsg implements Serializable {
#NotNull(message = "Msg Num is a required field")
private String msgNumber;
#NotNull(message = "Activity Name is a required field")
private String activityName;
}
This is not working
Please let me know how to achieve this
Below is an example of how you could use the ValidatorFactory to get a Validator to do the validation rather than using the #Valid annotation.
InternalMsg internalMsg = new InternalMsg();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<InternalMsg>> validate = validator.validate(internalMsg);
See here for more details -> https://www.baeldung.com/javax-validation
The below is just a snippet and not necessarily the recommended way of using the ValidationFactory
Related
I am using spring-boot-starter-validation dependency to validate java beans for my application
My Controller code as below
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#Valid #RequestBody InternalMsg internalMsg) {
return ResponseEntity.ok("Valid Message");
}
InternalMsg class
public class InternalMsg implements Serializable {
#NotNull(message = "Msg Num is a required field")
private String msgNumber;
#NotEmpty(message = "Activity Name is a required field")
private String activityName;
}
InternalMsg Sample Input JSON
{
"MSGNUMBER": "12345",
"ACTIVITYNAME": "",
}
My handler code as below
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<InternalMsgResponse> handleValidationExceptions(
MethodArgumentNotValidException ex) {
InternalMsgResponse msg = new InternalMsgResponse();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String errorMessage = error.getDefaultMessage(); // Error Message
ack.setStatusNotes(errorMessage);
});
return ResponseEntity.ok().body(msg);
}
As per the sample input, the activity name is empty so I get a validation error message as "Activity Name is a required field".
For some other processing, I need to get the values of input Msg inside handler i.e I want to get msgNumber value(12345) which is a VALID value.
Is it possible? If so, please guide how to retrieve those values inside the handler.
You can call
ex.getTarget()
That will return the object. Then simply cast it to InternalMsg
If you are using an older Spring Boot Version before 2.4.x then you must call
ex.getBindingResult().getTarget()
I'm using http://immutables.github.io in my SpringBoot project. I added Javax validation annotations in order to validate class but it did not work.
I tried to do this way:
#Value.Immutable
#JsonDeserialize(builder = UserSignUpRequest.Builder.class)
#JsonSerialize(as = UserSignUpRequest.class)
#JsonIgnoreProperties(ignoreUnknown = true)
#ImmutableJsonModel
interface AbstractUserSignUpRequest {
Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
#Value.Parameter
#Pattern(regexp = RegexPatterns.PHONE_REGEX)
String phone();
#Value.Parameter
#NotBlank
String username();
#Value.Parameter
Optional<DeviceRegistrationRequest> device();
#Value.Check
default void check() {
Set<ConstraintViolation<AbstractUserSignUpRequest>> violations = VALIDATOR.validate(this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
In controller body like this:
#PostMapping(
value = "/sign-up",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<UserCreatedResponse>> signUp(
#RequestBody Mono<#Valid UserSignUpRequest> request) {
return request
.flatMap(authenticationService::signUp)
.map(user -> ResponseEntity.status(CREATED).body(user));
}
My code did not work, I could not validate class with this way.
How should be implemented javax validation annotations in immutables classes?
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 controller's method with a getMapping and a pathValue:
#ResponseBody
#Validated
#GetMapping(produces = [MediaType.APPLICATION_JSON_UTF8_VALUE],
value = '/{person}')
PersonResponse getPersonInfo (
#Valid #PersonValidation
#PathVariable('person') String personId,
BindingResult bindingResult
) {
// Abort if received data is not valid
if (bindingResult.hasErrors()) {
throw new BadHttpRequest()
}
What I am trying to achieve is validating the uri's personId using my custom validation #PersonValidation but it is not working, the code is not giving any errors but the program never goes into my custom validation class...
¿How can I fix this?
¿How can I validate my path variable with a custom validation?
I have solved my problem. Spring have some problems to validate path variables...
First, you need to add a configuration class with a MethodValidationPostProcessor like this one:
/**
* this class is required to use our custom path variable validation on
* the controller class
*/
#Configuration
class ValidatorConfiguration {
#Bean
MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor()
}
}
Now, on the controller class, it is important to add #Validated annotation
#RestController
#Validated
class YourController class {
}
now your custom validation on the path variable is gonna work, if you want to catch the exception, add this method inside a exception handler class:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(value = [ ConstraintViolationException.class ])
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
String handle(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations()
StringBuilder strBuilder = new StringBuilder()
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n")
}
return strBuilder.toString()
}
}
I am building web service with spring and come across with following problem.
There is a post service as follow.
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
//post data
//return response
}
public class ADto{
private String firstParam;
private String secondParam;
// getter setter
}
So, my question is how can I know whether value of firstParam and secondParam is provided in request body or not.
RequestBody: { paramFirst: null, paramSecond: null}
Edit1:
Sorry for incomplete question:
For RequestBody: {paramFirst: first Value} and for above request value of paramSecond will be null.
So, how would I know whether paramSecond is included in request or not.
Edit2:
I don't want to validate. What I want to know is whether
request contains a particular parameter or not.
Because there are two different cases, one is value of a parameter is given null and other is paramter is not included in request.
You could use the #Valid annotation like so (pseudo code, didn't test it):
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#Valid #RequestBody ADto aDto){
// MethodArgumentNotValidException will be thrown if validation fails.
}
You'll need an exception handler to handle the validation error.
#ExceptionHandler
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public Error handleException(MethodArgumentNotValidException exception) {
//do something with the validation message: exception.getBindingResult()
}
And your class.
public class ADto{
#NotNull(message = "First parameter can not be null")
private String firstParam;
#NotNull(message = "Second parameter can not be null")
private String secondParam;
// getter setter
}
Try using Hibernate Validator (http://hibernate.org/validator/), it's really easy to integrate it with Spring.
That way, you'll need to annotate your Dto to enforce validation of required params and then call validate.
public class ADto{
#NotNull
private String firstParam;
#NotNull
private String secondParam;
// getter setter
}
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
validator.validate(aDto)
//post data
//return response
}
You could make firstParam and secondParam type Optional:
ADto class
public class ADto {
private Optional<String> firstParam;
private Optional<String> secondParam;
// getter setter
}
postSomething method
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto) {
if (Optional.ofNullable(aDto.getFirstParam()).isPresent()) {
// firstParam is provided in the request
} else {
// firstParam is not provided in the request
}
if (Optional.ofNullable(aDto.getSecondParam()).isPresent()) {
// secondtParam is provided in the request
} else {
// secondtParam is not provided in the request
}
}
Note that isPresent() will return false if and only if firstParam (as well as for secondParam) is not present in the request. Otherwise, even if the value is set to null, it will return true.