Spring 4.x/3.x (Web MVC) REST API and JSON2 Post requests, how to get it right once for all?
Asked Answered
E

2

43

Before going into details, I know there has been lots of conversations and related questions on Stackoverflow. All of them kind of help me in different ways so I thought I put my findings all together as a single organized FAQ to summarize my findings.

Related Concepts

Surely you know about these but I just write them as a quick review. Feel free to edit in case I am missing something.

HTTP POST Request:

A post request is used when you are willing to send an object to a web service or a your server side application.

Serialization:

Is the process of getting the object from your web browser through to your server side application. A jQuery Ajax call or a Curl post request can be used.

Serialization protocols:

The most popular ones theses days are JSON and XML. XML is becoming less popular as serialized xml objects are relatively bigger in size due the the nature of XML tagging. In this FAQ the main focus is JSON2 serialization.

Spring:

Spring framework and its powerful annotation makes it possible to expose web service in an efficient way. There are a lot of different libraries in Spring. The one that is our focus here is Spring web MVC.

Curl vs JQuery:

These are the tools you can use to make a post request in your client side. Even if you are planning to use JQuery ajax call, I suggest you use Curl for debugging purposes as it provides you with a detailed response after making the post request.

@RequestBody vs @RequestParam/@PathVariable vs @ModelAttribute:

In cases where you have a web service that is not depending on your Java EE model, @RequestBody must be used. If you are using the model and your JSON object is added to the model, you can access the object through @ModelAttribute. Only for cases where your request is either a GET request or a GET and POST request combination you will need to use @RequestParam/@PathVariable.

@RequestBody vs @ResposeBody:

As you can see from the name it as simple as that, you only need the @ResponseBody if you are sending a response the the client after the server side method processed the request.

RequestMappingHandlerAdapter vs AnnotationMethodHandlerAdapter:

RequestMappingHandlerAdapter is the new mapping handler for Spring framework that replaced AnnotationMethodHandlerAdapter since Spring 3.1. If your existing configuration is still in AnnotationMethodHandlerAdapter you might find this post useful. The config provided in my post will give you an idea on how to set up the RequestMappingHandlerAdapter.

Setup

You will need to setup a message convertor. This is how your serialized JSON message body is converted into a local java object at your server side.

Basic Configuration from here. The convertors were MarshallingHttpMessageConverter and CastorMarshaller in the basic configuration sample, I have replaced them with MappingJackson2HttpMessageConverter and MappingJacksonHttpMessageConverter.

Where to put the configuration

The way my project is set up, I have two config files:

  • Application Context XML: One it the application context XML file where your sessionFactory bean, dataSource bean, etc. are located.
  • MVC Dispatcher Servlet XML: This is where you have your view resolver bean and import your application context XML.

hadlerAdapter bean has to be located in the later that is the MVC Dispatcher XML file.

<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
            <ref bean="jsonConverter"/>

        </list>

    </property>
    <property name="requireSession" value="false"/>

</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="supportedMediaTypes" value="application/json"/>
</bean>

You can have multiple message convertors. here, I have created a normal JSON as well as a JSON 2 message convertor. Both Ref and normal bean format in the XML file have been used (personally I prefer the ref tag as its neater).

REST API

Here is a sample controller that is exposing the REST API.

The controller

This is where your REST API for a HTTP post request is exposed.

@Component
@Controller
@RequestMapping("/api/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String insertUser(@RequestBody final User user) {
    System.out.println(user.toString());
    userService.insertUser(user);
    String userAdded = "User-> {" + user.toString() + "} is added";
    System.out.println(userAdded);
        return userAdded;
    }
}

The Java Object

@JsonAutoDetect
public class User {

private int id;
private String username;
private String name;
private String lastName;
private String email;

public int getId() {
    return externalId;
}

public void setId(final int id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(final String name) {
    this.name = name;
}

public String getEmail() {
    return email;
}

public void setEmail(final String email) {
    this.email = email;
}
public String getUsername() {
    return username;
}

public void setUsername(final String username) {
    this.username = username;
}

public String getLastName() {
    return lastName;
}

public void setLastName(final String lastName) {
    this.lastName = lastName;
}

@Override
public String toString() {
    return this.getName() + " | " + this.getLastName()  + " | " + this.getEmail()
            + " | " + this.getUsername()  + " | " + this.getId()  + " | ";
    }

}

CURL Post call

curl -i -H "Content-Type: application/json" -X POST -d '{"id":100,"username":"JohnBlog","name":"John","lastName":"Blog","email":"[email protected]"}' http://localhost:8080/[YOURWEBAPP]/api/user/add

Related posts and questions

This FAQ was not possible if it wasn't for all the people who provided the following posts and questions (this list will expand if I come across useful related posts/questions):

  1. What is the correct JSON content type?
  2. Spring 3.0 making JSON response using jackson message converter
  3. How to POST JSON data with Curl from Terminal/Commandline to Test Spring REST?
  4. Posting JSON to REST API
  5. https://github.com/geowarin/spring-mvc-examples
  6. How to post JSON to PHP with curl
  7. Spring REST | MappingJacksonHttpMessageConverter produces invalid JSON
  8. https://github.com/eugenp/REST
  9. Spring Web MVC - validate individual request params
  10. How to POST JSON data with Curl from Terminal/Commandline to Test Spring REST?
  11. How do you return a JSON object from a Java Servlet
  12. What MIME type if JSON is being returned by a REST API?
Epithelium answered 4/6, 2013 at 4:18 Comment(6)
Great tutorial! Keep it upPaugh
@Jose, glad you liked it mate :)Epithelium
A small inconsistency in the Setup part. You use the first time MappingJacksonHttpMessageConverter (which is Jackson 1.*) and the second time MappingJackson2HttpMessageConverter (which is jackson 2.* compatible)Outpoint
@IliasBartolini I refere you to my note underneath the XML setting: You can have multiple message convertors. here, I have created a normal JSON as well as a JSON 2 message convertor. Both Ref and normal bean format in the XML file have been used (personally I prefer the ref tag as its neater).Epithelium
Where will I place the "handlerAdapter"? Will I place it inside mvc:annotation-driven?Haya
@Haya the handlerAdapter bean seats with the rest of your beans in your MVC Dispatcher XML file. This is where you have your view resolver bean and import your application context XMLEpithelium
E
13

CURL Post call

curl -i -H "Content-Type: application/json" -X POST -d '{"id":100,"username":"JohnBlog","name":"John","lastName":"Blog","email":"[email protected]"}' http://localhost:8080/[YOURWEBAPP]/api/user/add

Different Error Scenarios:

Here I explore different errors you might come across after you have made a curl call and what might have possibly gone wrong.

Scenario One:

HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 949
Date: Tue, 04 Jun 2013 02:59:35 GMT

This implies that the REST API does not exist in the URL you have provide.

Root cause:
  • You might have a typo in your request (believe me this can happen)!
  • It could be that your spring configuration is not right. If this is the case it needs further digging into what has actually gone wrong but I have provided some initial actions you need to do before start your more sophisticated investigation.
Actions:

After you have made sure that everything is done perfectly right and nothing is wrong with your Configuration nor you URL: - Run a maven clean. - Undeploy your web app or simply delete it. - Redeploy the web app - Make sure to use only one version of Spring in your maven/gradle

Scenario Two:

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 968
Date: Tue, 04 Jun 2013 03:08:05 GMT
Connection: close

The only reason behind this is that fact that your request is not formatted correctly. If you checkout the detailed curl response you should be able to see "The request sent by the client was syntactically incorrect.".

Root cause:

Either your JSON format is not right or you are missing a mandatory parameter for the JAVA object.

Actions:

Make sure you provide the JSON object in correct format and with the right number of parameters. Nullable properties are not mandatory but you do have to provide data for all NotNullable properties. It is VERY important to remember that Spring is using Java reflection to turn yours JSON file into Java objects, what does this mean? it means that variable and method names are CasE SensItiVe. If your JSON file is sending the variable "userName", than your matching variable in your Java object MUST also be named "userName". If you have getters and setters, they also have to follow the same rule. getUserName and setUserName to match our previous example.

Senario Three:

HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1051
Date: Wed, 24 Aug 2011 08:50:17 GMT
Root cause:

The Json media type is not supported by your web service. This could be due to your annotation not specifying the media type or you not specifying the media type in Curl post command.

Actions:

Check your message convertor is set up correctly and make sure the web service annotation matches the example above. If these were fine, make sure you specify the content-type in your Curl post request.

The json media type is not supported by your web service.

Senario N(!):

HTTP/1.1 200 OK 
Server: Apache-Coyote/1.1 
Content-Type: application/json;charset=UTF-8 
Transfer-Encoding: chunked 
Date: Tue, 04 Jun 2013 03:06:16 GMT 

Congrats the user is actually send to your server side REST API.

For further details on how to set up your spring checkout the spring mvc guide.

Related posts and questions

This FAQ was not possible if it wasn't for all the people who provided the following posts and questions (this list will expand if I come across useful related posts/questions):

  1. What is the correct JSON content type?
  2. Spring 3.0 making JSON response using jackson message converter
  3. How to POST JSON data with Curl from Terminal/Commandline to Test Spring REST?
  4. Posting JSON to REST API
  5. https://github.com/geowarin/spring-mvc-examples
  6. How to post JSON to PHP with curl
  7. Spring REST | MappingJacksonHttpMessageConverter produces invalid JSON
  8. https://github.com/eugenp/REST
  9. Spring Web MVC - validate individual request params
  10. How to POST JSON data with Curl from Terminal/Commandline to Test Spring REST?
  11. How do you return a JSON object from a Java Servlet
  12. What MIME type if JSON is being returned by a REST API?
Epithelium answered 4/6, 2013 at 4:18 Comment(0)
L
1

Should be good to notice that a bean class can NOT be handled if it has 2 or more setter for one field without @JsonIgnore on optional ones. Spring/Jackson throw HttpMediaTypeNotSupportedException and http status 415 Unsupported Media Type.

Example :

@JsonGetter
public String getStatus() {
    return this.status;
}

@JsonSetter
public void setStatus(String status) {
    this.status = status;
}

@JsonIgnore
public void setStatus(StatusEnum status) {
    if (status == null) {
        throw new NullPointerException();
    }

    this.status = status.toString();
}

Update : We also have to specify @JsonGetter and @JsonSetter in this case, not to have issues when used as return type.

Just tested it with Spring 3.2.2 and Jackson 2.2. It works fine as parameter (@RequestBody) and/or as return type (@ResponseBody).

Update 2 :

If @JsonGetter and @JsonSetter are specified, @JsonIgnore seems not to be required.

Libbey answered 5/6, 2013 at 8:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.