Spring doesn't validate JSON request - java

When I send the request:
"Person": {
"name": 5
}
The request should fail (bad request) because 5 isn't a String. It prints: Person{name='5'}.
Similarly, there's no error when I send null.
I have these annotations:
#JsonProperty("name")
#Valid
#NotBlank
private String name;
Controller:
public void register(#Valid #RequestBody Person p) {
...
}
How can I make it validate the name so only strings are accepted?

Add a BindingResult parameter.
public void register(#Valid #RequestBody Person p, BindingResult result) {
if (result.hasErrors()) {
// show error message
}
}

How can I make it validate the name so only strings are accepted?
Use the #Pattern annotation.
#JsonProperty("name")
#Valid
#NotBlank
#Pattern(regexp="^[A-Za-z]*$", message = "Name should contains alphabetic values only")
private String name;
For more details check this link and this one for the regex.

Related

Spring boot - class validation not working on REST API

I have the following REST API:
#ResponseBody
#PostMapping(path = "/configureSegment")
public ResponseEntity<ResultData> configureSegment(#RequestParam() String segment,
#RequestBody #Valid CloudSegmentConfig segmentConfig
) {
CloudSegmentConfig:
#JsonProperty(value="txConfig", required = true)
#NotNull(message="Please provide a valid txConfig")
TelemetryConfig telemetryConfig;
#JsonProperty(value="rxConfig")
ExternalSourcePpdkConfig externalSourcePpdkConfig = new ExternalSourcePpdkConfig(true);
TelemetryConfig:
public class TelemetryConfig {
static Gson gson = new Gson();
#JsonProperty(value="location", required = true)
#Valid
#NotNull(message="Please provide a valid location")
Location location;
#Valid
#JsonProperty(value="isEnabled", required = true)
#NotNull(message="Please provide a valid isEnabled")
Boolean isEnabled;
Location:
static public enum Location {
US("usa"),
EU("europe"),
CANADA("canada"),
ASIA("asia");
private String name;
private Location(String s) {
this.name = s;
}
private String getName() {
return this.name;
}
}
When I'm trying to send the following JSON:
{
"txConfig": {
"location": "asdsad"
}
}
The API return empty response 400 bad request, while I expect it to validate the location to be one of the ENUMs of the class. I also expect it to validate the isEnable parameter while it doesn't although I added all possible annotation to it..
Any idea?
Use #Valid annotation on TelemetryConfig telemetryConfig and no need to use #Valid on the field of TelemetryConfig class.
#Valid
TelemetryConfig telemetryConfig;
And for enum subset validation you can create a customer validator with annotation and use it.
A good doc about this Validating a Subset of an Enum

How to stop after first constraint in springboot for validation

i have conrtoller advice:
#ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException constraintViolationException) {
return new ResponseEntity<>(constraintViolationException.getMessage(), HttpStatus.BAD_REQUEST);
}
i have a controller:
#PostMapping(value="linkSellerBuyer")
#Validated
public String createSellerBuyer(Seller seller, Buyer buyer) {
// some code
return "some response";
}
each model are like:
public class Seller {
#NotEmpty String Email;
// and more and more
}
public class Buyer {
#NotEmpty String Email;
// and more and more
}
the error i get is i always get both seller and buyer errors
eg:
seller: must not be empty, buyer: must not be empty,
i want is
seller: must not be empty
?how can i create code for this?
If you check this out you will see that it validate all parameters at once.
But if you really want only one and first message you can extract it in your handleConstraintViolationException:
#ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException constraintViolationException) {
return new ResponseEntity<>(constraintViolationException.getConstraintViolations()
.iterator().next().getMessage(),
HttpStatus.BAD_REQUEST);
}

How can I detect if the JSON object within Request body is empty in Spring Boot?

I want to return an error when the body of a REST request is empty (e.g contains only {}) but there is no way to detect if the request body contains an empty JSON or not.
I tried to change #RequestBody(required = true) but it's not working.
#PatchMapping("{id}")
public ResponseEntity<Book> updateAdvisor(#PathVariable("id") Integer id,
#Valid #RequestBody BookDto newBook) {
Book addedBook = bookService.updateBook(newBook);
return new ResponseEntity<>(addedBook,HttpStatus.OK);
}
If the body sent contains an empty JSON I should return an exception.
If the body is not empty and at least one element is provided I won't return an error.
Try #RequestBody(required = false)
This should cause the newBook parameter to be null when there is no request body.
The above still stands and is the answer to the original question.
To solve the newly edited question:
Change the #RequestBody BookDto newBook parameter to a String parameter
(for example, #RequestBody String newBookJson).
Perform pre-conversion validation (such as, "is the body an empty JSON string value").
If the body contains valid JSON,
parse the JSON into to an object (example below).
#Autowired
private ObjectMapper objectMapper; // A Jackson ObjectMapper.
#PatchMapping("{id}")
public ResponseEntity<Book> updateAdvisor(
#PathVariable("id") Integer id,
#Valid #RequestBody String newBookJson)
{
if (isGoodStuff(newBookJson)) // You must write this method.
{
final BookDto newBook = ObjectMapper.readValue(newBookJson, BookDto.class);
... do stuff.
}
else // newBookJson is not good
{
.. do error handling stuff.
}
}
Let's suppose you have a Class BookDto :
public class BookDto {
private String bookName;
private String authorName;
}
We can use #ScriptAssert Annotation on Class BookDto:
#ScriptAssert(lang = "javascript", script = "_this.bookName != null || _this.authorName != null")
public class BookDto {
private String bookName;
private String authorName;
}
then in the resource/controller Class:
#PatchMapping("{id}")
public ResponseEntity<Book> updateAdvisor(#PathVariable("id") Integer id,
#Valid #RequestBody BookDto newBook) {
Book addedBook = bookService.updateBook(newBook);
return new ResponseEntity<>(addedBook,HttpStatus.OK);
}
Now #Valid annotation will validate whatever we have asserted in the #ScriptAssert annotation's script attribute. i.e it now checks if the body of a REST request is empty (e.g contains only {}).

Problem with form input validation in using #Valid and BindingResult in a Spring Boot App

I am trying to add code-side validation to my form. I am basing on this tutorial: https://www.javacodegeeks.com/2017/10/validation-thymeleaf-spring.html - but without effort.
I have an entity InvoiceData:
#Data
#Document
#NoArgsConstructor
public class InvoiceData {
#Id private String id;
private ContractorData data;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date receptionDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date orderDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date invoiceIssueDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
#NotNull
private Date contractDate;
#NotBlank
private String invoiceNumber;
private String additionalCosts;
private String contractorComment;
#NotEmpty
private List<InvoiceTask> invoiceTasks = new ArrayList<>();
And a Controller method:
#RequestMapping(value = "/addinvoice/{contractorId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(#PathVariable("contractorId") String contractorId, #ModelAttribute #Valid InvoiceData data, Model model, BindingResult result, RedirectAttributes attr, HttpSession session) {
if (result.hasErrors()) {
System.out.println("BINDING RESULT ERROR");
attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
attr.addFlashAttribute("register", result);
return "redirect:/add";
} else {
Contractor contractor = contractorRepository.findById(contractorId).get();
data.setData(contractor.getContractorData());
if (contractor.getInvoices() == null) {
contractor.setInvoices(new ArrayList<InvoiceData>());
}
contractor.getInvoices().add(data);
invoiceDataRepository.save(data);
contractorRepository.save(contractor);
model.addAttribute("contractor", contractor);
return "index";
}
}
And a small piece of the Thymeleaf for clearness (all other fields look alike this one)
<form action="#" th:action="#{addinvoice/{id}(id=${contractorid})}" th:object="${invoicedata}" method="post">
<ul class="form-style-1">
<li>
<label>Reception date<span class="required">*</span></label>
<input type="date" th:field="*{receptionDate}" id="receptionDate">
</li>
The problem is that when I am trying to send an invalid form, I am not redirected to /add, but I get an error page saying:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='invoiceData'. Error count: 6
And the stacktrace (from just one field, for clearness):
Field error in object 'invoiceData' on field 'invoiceIssueDate': rejected value [null]; codes [NotNull.invoiceData.invoiceIssueDate,NotNull.invoiceIssueDate,NotNull.java.util.Date,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [invoiceData.invoiceIssueDate,invoiceIssueDate]; arguments []; default message [invoiceIssueDate]]; default message [must not be null]
So I presume that this is one of the behaviours that I can exptect from the validator.
But there is one thing, when I set a breakpoint in the controller, at the beginning of the method where the if statement begins, AND I send an invalid form, the debugger never stops there, so it seems that this code is never reached...
But when I send a correctly filled form - everything goes fine, the code works, data is sent to the database etc...
My question is: is this a normal behaviour of the validator? What can I do make the code run when form is invalid, so I can get the BindingResult and show some error output to the user?
You need to move the BindingResult parameter right next to parameter having #Valid annotation.
#RequestMapping(value = "/addinvoice/{contractorId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(#PathVariable("contractorId") String contractorId, #ModelAttribute #Valid InvoiceData data, BindingResult result, Model model , RedirectAttributes attr, HttpSession session) {
if (result.hasErrors()) {
System.out.println("BINDING RESULT ERROR");
attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
attr.addFlashAttribute("register", result);
return "redirect:/add";
} else {
Contractor contractor = contractorRepository.findById(contractorId).get();
data.setData(contractor.getContractorData());
if (contractor.getInvoices() == null) {
contractor.setInvoices(new ArrayList<InvoiceData>());
}
contractor.getInvoices().add(data);
invoiceDataRepository.save(data);
contractorRepository.save(contractor);
model.addAttribute("contractor", contractor);
return "index";
}
}
Now the BindingResult variable will be attached to InvoiceData variable. Also if you are Validating multiple parameters in a API, you would require to declare its corresponding BindingResult variable right next to all of these.

Spring mvc validate a primitive with #RequestBody doesn't work

I'm trying to validate an email that I receive from the post request body but it's doesn't work !
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public String requestFoo(#RequestBody #Email String email) {
return email;
}
When I send a request with a string that doesn't respect the email regex the function is executed and I receive a 200 status code.
Even do I add the #Valid annotation the result is always the same.
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public String testValidation(#Valid #RequestBody #Email String email) {
return email;
}
Start with Spring 3.2 #RequestBody method argument may be followed by Errors object, hence allowing handling of validation errors in the same #RequestMapping :
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public ResponseEntity<String> testValidation(#Valid #RequestBody #Email String email, Errors errors) {
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(ValidationErrorBuilder.fromBindingErrors(errors));
}
return email;
}
And create a custom validator :
public class ValidationErrorBuilder {
public static ValidationError fromBindingErrors(Errors errors) {
ValidationError error = new ValidationError("Validation failed. " + errors.getErrorCount() + " error(s)");
for (ObjectError objectError : errors.getAllErrors()) {
error.addValidationError(objectError.getDefaultMessage());
}
return error;
}
}

Categories