Binding data to RequestBody (415 error) - java

I have the following in my Controller:
#Controller
public class GreetingController {
#PostMapping("/register")
public String registerUser(#RequestBody UserEntity request) throws ServletException, IOException {
System.out.println(request.getId());
return "register";
}
}
The UserEntity is:
#Entity
#Table(name = "users")
public class UserEntity {
private int id;
private String name;
private String email;
private String password;
I get the following error:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
Note that I have Jackson installed (from this question: Jackson Databind classpath issue).
Additionally, I am able to use the public String registerUser(HttpServletRequest request) fine, but when I try using #RequestBody it just gives me that error.
How would I get the #RequestBody to be the UserEntity?

You are using the header value "application/x-www-form-urlencoded;charset=UTF-8" in the request while you should use "application/json"

Related

springboot #validated and bindingresult

#Controller
#RequestMapping("/")
#Validated
public class AddressWebController {
#GetMapping(value = {"/{id}")
public String editForm(#PathVariable(value = "id") #Positive Long id, Model model) {
// process
}
#PutMapping(value = {"/edit"})
public String editProcess(#ModelAttribute #Valid Form form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// ???
}
// process
}
}
public class Form {
#NotBlank
private String name;
}
curl -XGET 'http://localhost/5'
is pass
curl -XGET 'http://localhost/-5'
A ConstraintViolationException exception is thrown. Due to #Validated and #Positive
curl 'http://localhost/edit' --data-raw '_method=PUT&name='
I expected it to come in as bindingResult.hasErrors() in that part, but the same ConstraintViolationException is thrown.
When #Validated is removed, bindingResult.hasErrors() works, but #Positive annotation does not.
How can I use #Validated annotation and BindingResult together?

How can accept both JSON and XML?

I saw similar question, and added to consumes parameters both MediaType. But it can't help me :(
I want to accept requests in json and xml formats.
There is my controller:
#RequestMapping(value = "/api/client", produces = "application/json;charset=UTF-8",
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public class ClientController {
#Autowired
private ClientService clientService;
#PostMapping("/create")
public String createClient(#Valid #RequestBody ClientDto clientDto) {
return clientService.createUser(clientDto);
}
}
Dto class:
import javax.validation.constraints.NotNull;
public class ClientDto {
private long client_id;
#NotNull
private String first_name;
#NotNull
private String last_name;
private List<AccountDto> accounts;
getter//setters//constructor//
}
I sending requests from Postman. I haven't problems with json.
But I can't accept xml format.
2021-08-15 20:08:43.600 WARN 15820 --- [nio-8081-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/xml;charset=UTF-8' not supported]
My problem was solved by #XmlRootElement annotation
#XmlRootElement
public class ClientDto {
private long client_id;
private String first_name;
private String last_name;
private List<AccountDto> accounts;
...
}
and
#XmlRootElement
public class AccountDto {
...
}

Spring Boot Test: UserControllerTest Fails When Using Jackson ObjectMapper To Convert Model To JSON String

I'm testing a #RestContoller in Spring Boot which has a #PostMapping method and the method #RequestBody is validated using #Valid annotation. To test it, I'm using MockMvc and in order to populate request body content I'm using Jackson ObjectMapper; however, when the model is passed, the test fails:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/user/register
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.springboottutorial.todoapp.controller.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.String> com.springboottutorial.todoapp.controller.UserController.register(com.springboottutorial.todoapp.dao.model.User)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :400
User Model:
#Entity
#Table(name = "users",
uniqueConstraints = #UniqueConstraint(columnNames = {"EMAIL"}))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "FIRST_NAME")
#NotNull
private String firstName;
#Column(name = "LAST_NAME")
#NotNull
private String lastName;
#Column(name = "EMAIL")
#NotNull
#Email
private String emailAddress;
#Column(name = "PASSWORD")
#NotNull
private String password;
#Column(name = "CREATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime createdAt;
#Column(name = "UPDATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime updatedAt;
public User(#NotNull String firstName, #NotNull String lastName,
#NotNull #Email String emailAddress, #NotNull String password,
#NotNull LocalDateTime createdAt, #NotNull LocalDateTime updatedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
this.password = password;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
//setters and getters: omitted
UserController:
#RestController
#RequestMapping("/api/user")
public class UserController {
#Autowired
UserService userService;
#PostMapping("/register")
public ResponseEntity<String> register(#RequestBody #Valid User user){
userService.createUser(user);
return ResponseEntity.ok().build();
}
}
UserControllerTest:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
When I build JSON string manually, the test passes:
String json = "{\n" +
"\t\"firstName\" : \"John\",\n" +
"\t\"lastName\" : \"QPublic\",\n" +
"\t\"password\" : \"123456789\",\n" +
"\t\"createdAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"updatedAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"emailAddress\" : \"john.public#gmail.com\"\n" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
Spring is not necessarily providing you with a "vanilla" ObjectMapper instance. By having Spring inject the ObjectMapper instance into the test instead of creating an ObjectMapper with the default constructor, you will get an instance that matches your actual run-time environment, provided that your spring profile for your unit tests is set up correctly.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper objectMapper;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(objectMapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
As chrylis mentioned in comments, the problem occurred due to Java 8 Date & Time API and Jackson serialization conflicts. By default, ObjectMapper doesn't understand the LocalDateTime data type, so I need to add com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency to my maven which is a datatype module to make Jackson recognize Java 8 Date & Time API data types. A stackoverflow question and a blog post helped me to figure out what actually my problem was.
If LocalDateTime is not a problem, I think we should implementequals in User class.
This is how I implement my rest controllers test. Maybe It could help you.
I've this abstract class to encapsulate common tests functionalities regarding to the JSON mapping.
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
public abstract class RestControllerTest {
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
StandardCharsets.UTF_8);
private HttpMessageConverter messageConverter;
protected MediaType getContentType() {
return contentType;
}
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
messageConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
messageConverter);
}
#SneakyThrows
protected String toJson(Object data) {
val mockHttpOutputMessage = new MockHttpOutputMessage();
messageConverter.write(
data, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
You could use it in your test class like this
#WebMvcTest(UserController.class)
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest extends RestControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(toJson(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
I hope it works for you

How to use #valid annotation in complex model

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

Spring boot, how to use #Valid with List<T>

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
)

Categories