This question already has an answer here:
Spring POST receives object with null values
(1 answer)
Closed 3 years ago.
I have an Android app contacting a REST API developed with Spring. The app serializes well an object but the Spring application parses every field as null when they aren't null. What can be wrong? what shall I do?
This is the REST API method signature: cambioPassword(#RequestHeader("Authorization") String header, PasswordChange userData)
Note: both the app and the API uses Gson (I'm not using Jackson as default in the Spring application)
I've tried every kind of constructor and I've changed the visibility of the Java class fields
public class PasswordChange {
private String name;
private String currentPassword; // Ciphered field
private String newPassword; // Ciphered field
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword= currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
The server should be able to deserialize the PasswordChange class and the fields should not be null as they aren't in the original sent JSON.
Could it be that you are missing the #RequestBody annotation, I expect it to look like:
#PostMapping("/cambio")
cambioPassword(#RequestHeader("Authorization") String header, #RequestBody PasswordChange userData){
...
}
See https://www.baeldung.com/spring-request-response-body
Related
I have a small program in spring-boot which through a get call having a #RequestBody returns me a message with all the specifications (in my case of cars)
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
I would like to be able to make sure that if a field is set to null, it can still find the relative message with the other fields having a value, in my case, I wanted to put that the "name" field is optional in the RequestBody, is it possible to do this? I tried setting
public CarsResponse getCars(#RequestBody (required = false) CarsRequest request) throws IOException {
//some code
}
but then when I go to do the get it completely deletes the null field at the time of the get and therefore fails to do it
Just remove the #RequestBody annotation from the function and keep it as it is
public CarsResponse getCars(CarsRequest request) throws IOException {
//some code
}
Now all fields will be converted into query params and all will be optional, because query param by convention are optional
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
And call like this
GET /someEndpoint?name=<value>&plate=null
But still if you want to make some params mandatory, then use javax.annotations or apply validation yourself.
EDIT: As asked in comment, if you are accepting JSON as parameter body then you can do one thing, you can accept it as String and then convert json to object inside function body
public CarsResponse getCars(#RequestParam(required = false) String request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
CarRequest request = mapper.readValue(request,CarRequest.class);
// other code
}
and call it something like this
GET /someEndpoint?request="{ \"name\" : null, \"plate\": \"someValue\" }"
EDIT 2:
You can do one more thing if you want to keep sending json and have it transformed into object, you can declare a binder something like this
// Some controller class
class SomeController {
#Autowired
ObjectMapper mapper;
// Ommited methods here
#GetMapping("/carRequest")
public ResponseEntity<String> testBinder(#RequestParam CarRequest request) {
return ResponseEntity.ok("{\"success\": \"" + request.name+ "\"}");
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CarRequest.class, new CarRequestEditor(mapper));
}
static class CarRequestEditor extends PropertyEditorSupport {
private ObjectMapper objectMapper;
public CarRequestEditor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
if (StringUtils.isEmpty(text)) {
setValue(new CarRequest());
} else {
try {
setValue(objectMapper.readValue(text, CarRequest.class));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}
}
}
Please note that the client need to send the json URL encoded like this
http://localhost:8180/carRequest?request=%7B%22name%22%3"test"%7D
Hi you are using #RequestBody (required = false) CarsRequest
that means your CarsRequest object itself is optional
rather than you can use
#NotEmpty
private String plate ;
#NotEmpty
private String price;
You can make a single field optional by making it an Optional, in your case Optional<String>. If the field does not appear in the request body, then the Optional will be empty.
public class CarsRequest implements Serializable {
private String name;
private String plate;
private Optional<String> price;
}
I created a program that communicates through http requests. I use Postman to send requests. When I make a registration, I invoke the API method. If this method throws some exceptions, I manage it with "HandlerMapping" which catches the exception and sends a personalized message about it.
HandlerMapping class:
#Provider
public class HandlerMapper implements ExceptionMapper<InputValidationException>{
#Override
public Response toResponse(InputValidationException exception) {
return Response.status(exception.getStatus().getStatusCode(), exception.getMessage()).build();
}
}
InputValidationException:
public class InputValidationException extends Exception{
private String errorMessage;
private Response.Status status;
#JsonbCreator
public InputValidationException (#JsonbProperty("message") String message, #JsonbProperty("status") Response.Status status) {
this.errorMessage = "Invalid param entered: " + message;
this.status = status;
}
.........
}
Now when it throws an exception from the API method it works and a customer message is sent as I would like. But if I send a message with the wrong param (for example with the name null), a custom response error is not created as I do with the api method, but the default message is created with 500 Server Error. How can I make a general class to handle errors in a personalized way?
Client Client
public class Client {
private String surname;
private String name;
private String city_of_birth;
.... another parameters ....
#JsonbCreator
public Cliente(#JsonbProperty("surname") String surname, #JsonbProperty("name") String name, #JsonbProperty("city_of_birth") String city_of_birth) throws InputValidationException {
paramValidation(surname, name, city_of_birth, ......... );
this.surname= surname;
this.name= name;
this.city_of_birth= city_of_birth;
.... another parameters ....
}
private void paramValidation(String surname, String name, String city_of_birth) throws InputValidationException{
if( surname == null || surname.isBlank() ){
throw new InputValidationException("surname", Response.Status.METHOD_NOT_ALLOWED);
}
.... other parameter controls ....
}
}
API class
#Path("homeBanking/client/signup")
public class Registration {
private DaoClient daoC = new DaoClient();
#POST
#Produces(MediaType.APPLICATION_JSON)
public Response createClient(Client client) throws InputValidationException {
daoC.insert(client);
return Response.ok().build();
}
}
I think in your context the solution is creating an Exception Mapper to convert the exception message into a JSON object, following this structure: https://docs.jboss.org/resteasy/docs/3.0.6.Final/userguide/html/ExceptionHandling.html
Using the Spring Framework, you can use the ControllerAdvice to create your own error messages. See this article: https://www.baeldung.com/exception-handling-for-rest-with-spring
I hope I've helped!
I am passing a request body to a POST request on postman similar to this:
"name":"Mars",
"artifacts":[
{
"elements":[
{
"name":"carbon",
"amount":0.5,
"measurement":"g"
}
],
"typeName":"typeA"
},
{
"elements":[
{
"name":"hydrogen",
"amount":0.2,
"measurement":"g"
}
],
"typeName":"typeB"
}
]
The create method in the rest controller looks like this.
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars;
Planet and all its nested objects have a default constructor such as:
public Planet() {}
However, I am not able to create a new planet object because of lack of a default constructor. Please help!
EDIT:
Planet class
public class Planet {
#JsonProperty("name")
private String name;
#Field("artifacts")
private List<Artifact> artifacts;
public Planet() {}
public Planet(String name, List<Artifact> artifacts)
{
this.name = name;
this.artifacts = artifacts;
}
//setters and getters
}
Artifact class:
public class Artifact() {
#Field("elements")
private List<Element> elements;
#JsonProperty("typeName")
private String typeName;
public Artifact() {}
public Artifact(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
}
Element class:
public class Element() {
#JsonProperty("elementName")
private String name;
#JsonProperty("amount")
private double amount;
#JsonProperty("measurement")
private String measurement;
public Element() {}
public Element(String name, double amount, String measurement)
{
//assignments
}
}
I had that the same error when I forgot the #RequestBody before the parameter
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
I don't understand what is the issue you are facing, but i can see an error straight away so guessing that is the issue you are facing, i am going to give you a solution.
Create a class which matches your json data structure like this :
Class PlanetData {
private String name;
private List<Planet> artifacts;
public PlanetData(String name, List<Planet> artifacts){
name = name;
artifacts = artifacts;
}
// include rest of getters and setters here.
}
Then your controller should look like this. Basically you needed to put #RequestBody to all the parameters you want to recieve from request JSON. Earlier you only put #RequestBody to name parameter not artifact parameter and since Request Body can be consumed only once, so you need a wrapper class to recieve the complete request body using single #RequestBody annotation.
#RequestMapping("/create")
public String create(#RequestBody PlanetData data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars.toString();
}
Edit : Looking at the Planet class, it also needs some modification
public class Planet {
private String typeName; // key in json should match variable name for proper deserialization or you need to use some jackson annotation to map your json key to your variable name.
private List<Element> elements;
public Planet() {}
public Planet(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
//setters and getters. Remember to change your setters and getter from name to typeName.
}
Hope this solves your issue.
This answer too might help someone.
When you are using spring framework for your API development, you may accidently import a wrong library for RequestBody and RequestHeader annotations.
In my case, I accidently imported library,
io.swagger.v3.oas.annotations.parameters.RequestBody
This could arise the above issue.
Please ensure that, you are using the correct library which is
org.springframework.web.bind.annotation.RequestBody
I guess, it’s trying to call new List() which has no constructor. Try using ArrayList in your signatures.
If it works this way, you have found the error. Then rethink your concept of calling methods, since you would usually want to avoid using implementations of List in method signatures
Make sure your request type is not of type GET
If so it is better not to send data as request body.
you should write as below:
...
public String create(#RequestBody JSONObject requestParams) {
String name=requestParams.getString("name");
List<Planet> planetArtifacts=requestParams.getJSONArray("artifacts").toJavaList(Planet.Class);
...
I need to have my Java server receive a PUT request to create a new user from an id and a json body, the URI needs to be like:
/usermanagement/user/$id { "name":john, "type":admin }
Given that I've made a simple Java class and can later convert the JSON to a POJO using Jackson, here's my problem:
How do I specify the PUT request to accept both the id and the JSON body as parameters? So far I've got:
#PUT
#Path("{id}")
#Consumes(MediaType.APPLICATION_JSON)
public String createUser(#PathParam("id") int id){
User user = new User();
User.setId(id);
return SUCCESS_MSG;
}
And this works, but I've had no luck adding the JSON body and having the function parse it. I've tried:
public String createUser(#PathParam("id") int id, String body){
return body;
}
It should return the same input JSON when testing in Postman, however it always returns a "resource not available" error.
I feel there's something obvious that I'm missing here?
As per REST API conventions, a POST method on a uri like /usermanagement/users is what is needed. PUT method is used for updating an existing resource. You can go through this wonderful article on how to design pragmatic RESTful API. http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api.
If you are trying to create a new user, why give it an ID? You have to POST the data such as user name, lastname, email, ... and let the backend generate an ID (like an auto-incremented id, or some UUUID) for this new resource.
For example, in my app, I use a json body for a POST request like below:
{
"loginId": "ravi.sharma",
"firstName": "Ravi",
"lastName": "Sharma",
"email": "myemail#email.com",
"contactNo": "919100000001",
"..." : ".."
}
Moreover, your response should return HTTP-201, after successful creation, and it should contain a location header, pointing to the newly created resource.
Instead of Using String body, use a Class with Member variables name and Type Like this.
public class User {
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
(This works in Spring Boot Web out-of-box. incase of Spring MVC, you might need to add Jackson dependency): On your Controller , Add #RequestBody Annotation, then Jackson will take care of the un-marshaling of JSON String to User Object.
public String createUser(#PathParam("id") int id, #RequestBody User user){
I am running the service under TomEE.
The model is very simple:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Card {
#XmlElement(required = true, nillable = false)
private String cardNumber;
public Card() {
//no-op
}
public Card(final String s) {
cardNumber = s;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
}
I followed this example
https://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced008.htm
The service is also very simple like:
#Consumes(APPLICATION_XML)
#Produces(APPLICATION_XML)
public class MyService {
#POST
#Path("status")
public String queryStatus(Card card) {
// do something
}
}
If my input is wrongly formatted, it will have a proper exception. But it doesn't seem to be able to validate empty card number or null.
For example, when I have
"<card></card>"
or
"<card><cardNumber> </cardNumber></card>"
(with an empty string), the service still goes through, with the "cardNumber" property being null or empty.
Well, I could do something in the setter to throw out an exception. But I was hoping JavaEE automatically handle this kind of this if I put the annotation on the property.
So what am I missing here?
Thank you for any tips!
With Bean Validation (http://beanvalidation.org/) Java EE offers a standard way to validate objects. It is also integrated with JAX RS.
So you can use annotations like #NotNull in your Card class. In your Service just say that you want a #Valid Card.
An example can be found here: https://jaxenter.com/integrating-bean-validation-with-jax-rs-2-106887.html