I am trying to put validation to a Spring Boot project. So I put #NotNull annotation to Entity fields. In controller I check it like this:
#RequestMapping(value="", method = RequestMethod.POST)
public DataResponse add(#RequestBody #Valid Status status, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return new DataResponse(false, bindingResult.toString());
}
statusService.add(status);
return new DataResponse(true, "");
}
This works. But when I make it with input List<Status> statuses, it doesn't work.
#RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(#RequestBody #Valid List<Status> statuses, BindingResult bindingResult) {
// some code here
}
Basically, what I want is to apply validation check like in the add method to each Status object in the requestbody list. So, the sender will now which objects have fault and which has not.
How can I do this in a simple, fast way?
My immediate suggestion is to wrap the List in another POJO bean. And use that as the request body parameter.
In your example.
#RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(#RequestBody #Valid StatusList statusList, BindingResult bindingResult) {
// some code here
}
and StatusList.java will be
#Valid
private List<Status> statuses;
//Getter //Setter //Constructors
I did not try it though.
Update:
The accepted answer in this SO link gives a good explanation why bean validation are not supported on Lists.
Just mark controller with #Validated annotation.
It will throw ConstraintViolationException, so probably you will want to map it to 400: BAD_REQUEST:
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice(annotations = Validated.class)
public class ValidatedExceptionHandler {
#ExceptionHandler
public ResponseEntity<Object> handle(ConstraintViolationException exception) {
List<String> errors = exception.getConstraintViolations()
.stream()
.map(this::toString)
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponseBody(exception.getLocalizedMessage(), errors),
HttpStatus.BAD_REQUEST);
}
private String toString(ConstraintViolation<?> violation) {
return Formatter.format("{} {}: {}",
violation.getRootBeanClass().getName(),
violation.getPropertyPath(),
violation.getMessage());
}
public static class ErrorResponseBody {
private String message;
private List<String> errors;
}
}
#RestController
#Validated
#RequestMapping("/products")
public class ProductController {
#PostMapping
#Validated(MyGroup.class)
public ResponseEntity<List<Product>> createProducts(
#RequestBody List<#Valid Product> products
) throws Exception {
....
}
}
with using Kotlin and Spring Boot Validator
#RestController
#Validated
class ProductController {
#PostMapping("/bulk")
fun bulkAdd(
#Valid
#RequestBody statuses: List<Status>,
): ResponseEntity<DataResponse>> {...}
}
data class Status(
#field:NotNull
val status: String
)
Related
I'm trying to post body in RESTAPI using spring boot but I cannot. I suffered for long hours till now I also could not find the problem why what is the reason I do not know what mistake I made please help me.
Pojo class
Person.java
package com.thila.family.test;
import javax.annotation.Generated;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class Person {
#Id
private int id;
private String familyName;
private String familyMembers;
private long contactNo;
public Person() {
}
public Person(int id,String familyName, String familyMembers,long contactNo) {
super();
this.familyName = familyName;
this.familyMembers = familyMembers;
this.contactNo = contactNo;
}
//getters setters
}
PersonService.java
package com.thila.family.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
#Service
public class PersonService {
private List<Person> persons=new ArrayList<>();
public void addPerson(Person person) {
persons.add(person);
}
}
PersonController.java
#RestController
public class PersonController {
#Autowired
private PersonService personService;
#RequestMapping(method=RequestMethod.POST,value="/fam")
public void addPerson(#RequestBody Person person){
personService.addPerson(person);
}
}
My JSON request to the body
{
"id":"1",
"familyName": "panchala",
"familyMembers":"5",
"contactNo":"234567"
}
I got an error
{
"timestamp": "2021-01-02T04:39:55.307+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/fam"
}
```
please help me I don't know why I got this error
In PersonController.java add the following annotation just below the #RestController annotation:
#RequestMapping(value = "/person", produces = MediaType.APPLICATION_JSON_VALUE)
Only if this is present the controller gets resolved and the path reaches the particular controller method you are trying to call.
Now your URL might look like this : localhost:8080/person/fam
In the Controller class
#RequestMapping(value = "/fam", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public void addPerson(#RequestBody Person person){
{
....
}
Just a suggestion it would be also good to return a response with a correct Response status
#RequestMapping(value = "/fam", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity addPerson(#RequestBody Person person){
{
......
return new ResponseEntity<>(HttpStatus.OK);
}
I had the same issue and I was not figuring out how to solve it, but I decided to see my imports, in case of imports being wrong.
I was importing #RequestBody from:
io.swagger.v3.oas.annotations.parameters.RequestBody;
Instead of importing the #RequestBody from:
org.springframework.web.bind.annotation.*
If any question, just comment.
I have complex model request class and I am using valid annotation. But it doesnt work in subclasses.
cause=java.lang.NullPointerException detailMessage=HV000028:
Unexpected exception during isValid call.
public class ChangeBlackListStatusRequest {
#Valid
List<CategoryChangeRequest> categoryChangeRequestList;
}
public class CategoryChangeRequest {
#Valid
List<Category> categoryList;
#Valid
List<Service> serviceList;
#Valid
List<Merchant> merchantList;
#Valid
List<Aggregator> aggregatorList;
}
New to spring ,
i am trying to access json object in #RequestBody MYPOJO pojo which works fine , but my json data needed to be same as variable name in pojo and case sensitive. best i did find from web is here , but not synchronize with my project , i am using spring mvc. So how can i make case insensitive my json with pojo?
the way i receive json
#RequestMapping(value = "create", method = RequestMethod.POST)
public void createPost(HttpServletRequest req, HttpServletResponse resp, #Valid #RequestBody Post post,
Errors errors) throws CustomException, IOException {
json data
function jsonForPost(isEdit, id) {
var post = {};
if (isEdit) {
post.id = id;
}
post.name = $("#name").val();
return JSON.stringify(post);
}
With Spring Boot
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.fasterxml.jackson.databind.MapperFeature;
#Configuration
class Configs {
#Bean
public Jackson2ObjectMapperBuilderCustomizer initJackson() {
Jackson2ObjectMapperBuilderCustomizer c = new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
}
};
return c;
}
}
Without Spring Boot
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.MapperFeature;
#Configuration
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
I have a POJO with a variable name in it:
public class Pox {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and a Controller:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public void createPost(HttpServletRequest req, HttpServletResponse resp, #Valid #RequestBody Pox post,
Errors errors) {
System.out.println(post.getName());
}
I have tested with Postman with:
name, Name, NAme, nAme.
All of them worked.
With springboot using the application.yml file =>
spring:
jackson:
mapper:
accept-case-insensitive-properties: true
Usually we write
#RestController
public class TestController {
#RequestMapping(value = "/test")
public String test2(#RequestBody #Valid TestClass req) {
return "test2";
}
}
But since it is a REST controller is it possible to configure Spring to use #RequestBody #Valid by default, so these annotations could be omitted?
I am new to Spring, and I am trying to create a RESTful resource to use on my API. So far, I was able to list elements (GET /people.json), show a specific element (GET /people/{id}.json), create a new element (POST /people.json), and delete an element (DELETE /people/{id}.json). My last step is to work on the update method (PUT /people/{id}.json, but I have no idea how to do it yet.
Here is the code I have for my controller:
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.model.Person;
import com.example.service.PersonService;
import java.util.Map;
#Controller
public class PersonController {
#Autowired
private PersonService personService;
#ResponseBody
#RequestMapping(value = "/people.json", method = RequestMethod.GET)
public String all(Map<String, Object> map) {
return "{\"people\": " + personService.all() + "}";
}
#ResponseBody
#RequestMapping(value = "/people/{personId}.json", method = RequestMethod.GET)
public String show(#PathVariable("personId") Integer personId) {
Person person = personService.find(personId);
return "{\"person\": "+person+"}";
}
#ResponseBody
#RequestMapping(value = "/people.json", method = RequestMethod.POST)
public String create(#ModelAttribute("person") Person person, BindingResult result) {
personService.create(person);
return "{\"person\": "+person+"}";
}
#ResponseBody
#RequestMapping(value = "/people/{personId}.json", method = RequestMethod.PUT)
public String update(#PathVariable("personId") Integer personId, #ModelAttribute("person") Person person, BindingResult result) {
personService.update(personId, person);
return "{\"person\": "+person+"}";
}
#ResponseBody
#RequestMapping(value = "/people/{personId}.json", method = RequestMethod.DELETE)
public String delete(#PathVariable("personId") Integer personId) {
personService.delete(personId);
return "{\"status\": \"ok\", \"message\": \"\"}";
}
}
And here is the code for my service:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.model.Person;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.List;
#Service
public class PersonServiceImpl implements PersonService {
#PersistenceContext
EntityManager em;
#Transactional
public List<Person> all() {
CriteriaQuery<Person> c = em.getCriteriaBuilder().createQuery(Person.class);
c.from(Person.class);
return em.createQuery(c).getResultList();
}
#Transactional
public Person find(Integer id) {
Person person = em.find(Person.class, id);
return person;
}
#Transactional
public void create(Person person) {
em.persist(person);
}
public void update(Integer id, Person person) {
// TODO: How to implement this?
}
#Transactional
public void delete(Integer id) {
Person person = em.find(Person.class, id);
if (null != person) {
em.remove(person);
}
}
}
So, for the Spring, hibernate experts out there, what is the best way to accomplish what I need? It is also important that the resource update only updates the actual changed attributes
Thanks,
Change your PersonService update method signature so that it returns a Person, also, you don't need an separate id, just use the Person object to hold the id. Them implement the method like this:
public Person update(Person person) {
// you might want to validate the person object somehow
return em.merge(person);
}
Also update your web service layer accordingly. Either change it so that personId is no longer there, or keep it and set the Person id with it:
public String update(#PathVariable("personId") Integer personId,
#ModelAttribute("person") Person person, BindingResult result) {
person.setId(personId);
Person updated = personService.update(person);
// You may want to use a JSON library instead of overriding toString.
return "{\"person\": " + updated + "}";
}
One small gotcha, merge may also persist new entities. So, if you want to make sure that update only updates existing ids, change its body to something like:
public Person update(Person person) {
if(em.find(Person.class, person.getId()) == null) {
throw new IllegalArgumentException("Person " + person.getId()
+ " does not exists");
}
return em.merge(person);
}
It is also important that the resource update only updates the actual changed attributes
If you want hibernate to update only attributes that are different between your JSON object and the database there is a handy combo of dynamicUpdate and selectBeforeUpdate properties on #org.hibernate.annotations.Entity (check out the Hibernate Manual). Only enable this if you really need the behavior (which is non standard behavior and may decrease performance).
#org.hibernate.annotations.Entity(dynamicUpdate = true, selectBeforeUpdate = true)
On Hibernate 4.0 those properties where deprecated, you can use individual annotations instead:
#DynamicUpdate
#SelectBeforeUpdate