Posting JSON to REST API

93,185

Solution 1

As sdouglass suggested, Spring MVC automatically detects Jackson and sets up a MappingJacksonHttpMessageConverter to handle conversion to/from JSON. But I did need explicity configure the converter to get it to work as he also pointed out.

I added the following and my CURL GET requests were working..Hooray.

AppConfig.java

@Configuration
@ComponentScan(basePackages = "com.app")
public class AppConfig {

    @Bean
    public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
    {
        final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

        HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

        String[] supportedHttpMethods = { "POST", "GET", "HEAD" };

        annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
        annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return annotationMethodHandlerAdapter;
    }
}


curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT

[{"id":1,"pan":111}]



But the following CURL POST was still not working (Never hitting the controller action and giving no console debug info.

curl -i -X POST -H "Content-Type:application/json"  http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close

The request sent by the client was syntactically incorrect ().



So I added Logback to get some detailed debugging started.

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>/home/thomas/springApps/purchaseapi.log</file>
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="org.hibernate" level="DEBUG" />

    <logger name="org.springframework" level="TRACE" />
    <logger name="org.springframework.transaction" level="INFO" />
    <logger name="org.springframework.security" level="INFO" /> <!-- to debug security related issues (DEBUG) -->
    <logger name="org.springframework.web.servlet.mvc" level="TRACE" /> <!-- some serialization issues are at trace level here: org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod -->

    <!-- our service -->
    <logger name="com.app" level="DEBUG" />
    <!-- <logger name="com.app" level="INFO" /> --><!-- to follow if setup is being executed -->

    <root level="INFO">
        <appender-ref ref="FILE" />
    </root>

</configuration>



Adding TRACE level debugging to org.springframework.web.servlet.mvc gave me the answer to the problem.

2012-04-28 14:17:44,579 DEBUG [http-bio-8080-exec-3] o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [AbstractMessageConverterMethodArgumentResolver.java:117] Reading [com.app.model.Purchase] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter@74a14fed]
2012-04-28 14:17:44,604 TRACE [http-bio-8080-exec-3] o.s.w.s.m.m.a.ServletInvocableHandlerMethod [InvocableHandlerMethod.java:159] Error resolving argument [0] [type=com.app.model.Purchase]
HandlerMethod details: 
Controller [com.app.controller.PurchaseController]
Method [public void com.app.controller.PurchaseController.create(com.app.model.Purchase)]

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unexpected character ('p' (code 112)): was expecting double-quote to start field name



I changed my CURL POSTs to the following an it all worked:

curl -i -X POST -H "Content-Type:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase -d '{"pan":11111}'
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Sat, 28 Apr 2012 13:19:40 GMT

Hopefully someone finds this useful.

Solution 2

If I recall correctly the Spring docs say that Spring MVC will automatically detect Jackson on the classpath and set up a MappingJacksonHttpMessageConverter to handle conversion to/from JSON, but I think I have experienced situations where I had to manually/explictly configure that converter to get things to work. You may want to try adding this to your MVC config XML:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
        </list>
    </property>
</bean>

UPDATE: It was this plus properly formatting the JSON being posted, see https://stackoverflow.com/a/10363876/433789

Solution 3

Its 2014 and I wanted to add a few updates to this question which helped me solve the same problem.

  1. Code update to replace deprecated AnnotationMethodHandlerAdapter in Spring 3.2

        @Configuration
        public class AppConfig {
    
    
        @Bean
        public RequestMappingHandlerAdapter  annotationMethodHandlerAdapter()
        {
            final RequestMappingHandlerAdapter annotationMethodHandlerAdapter = new RequestMappingHandlerAdapter();
            final MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
    
            List<HttpMessageConverter<?>> httpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
            httpMessageConverter.add(mappingJacksonHttpMessageConverter);
    
            String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
    
            annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
            annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
    
            return annotationMethodHandlerAdapter;
        }
    }
    
  2. HTTP/1.1 415 Unsupported Media Type error

After spending many hours trying to figure out why I am STILL GETTING a 415 error even after adding the correct JSON configuration I finally realized that the problem was NOT with the server side but with the client side. In order for Spring to accept your JSON you MUST make sure that you are sending both "Content-Type : application/json" and "Accept: application/json" as part of your http headers. for me specifically it was an android application HttpUrlConnection which I had to set as:

    public static String doPost(final String urlString,final String requestBodyString) throws IOException {
        final URL url = new URL(urlString);

        final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        try {
          urlConnection.setReadTimeout(10000 /* milliseconds */);
          urlConnection.setConnectTimeout(15000 /* milliseconds */);
          urlConnection.setRequestProperty("Content-Type", "application/json");
          urlConnection.setRequestProperty("Accept", "application/json");
          urlConnection.setDoOutput(true);
          urlConnection.setRequestMethod("POST");
          urlConnection.setChunkedStreamingMode(0);

          urlConnection.connect();

          final PrintWriter out = new PrintWriter(urlConnection.getOutputStream());
          out.print(requestBodyString);
          out.close();

          final InputStream in = new BufferedInputStream(urlConnection.getInputStream());
          final String response =  readIt(in);

          in.close(); //important to close the stream

          return response;

        } finally {
          urlConnection.disconnect();
        }
    }

Solution 4

Try adding a descriptor of what's in your POST request. That is, add to curl the header:

Content-Type: application/json

If you don't add it, curl will use the default text/html regardless of what you actually send.

Also, in PurchaseController.create() you have to add that the type accepted is application/json.

Solution 5

I had the same problem, which was solved by two changes in my code :

  1. Missing @PathVariable in my method argument, my method didn't have any
  2. Following method in my SpringConfig class since the one I had with handler interceptor was deprecated and giving some issue:

    public RequestMappingHandlerAdapter RequestMappingHandlerAdapter()
    {
        final RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
        final String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
    
        requestMappingHandlerAdapter.getMessageConverters().add(0, mappingJacksonHttpMessageConverter);
        requestMappingHandlerAdapter.setSupportedMethods(supportedHttpMethods);
    
        return requestMappingHandlerAdapter;
    }
    
Share:
93,185
Thomas Buckley
Author by

Thomas Buckley

Updated on November 24, 2020

Comments

  • Thomas Buckley
    Thomas Buckley over 3 years

    I'm creating a REST API that will accept JSON requests.

    I'm testing it out using CURL:

    curl -i -POST -H 'Accept: application/json' -d '{"id":1,"pan":11111}' http://localhost:8080/PurchaseAPIServer/api/purchase
    


    But getting the following error:

    HTTP/1.1 415 Unsupported Media Type
    Server: Apache-Coyote/1.1
    Content-Type: text/html;charset=utf-8
    Content-Length: 1051
    Date: Wed, 25 Apr 2012 21:36:14 GMT
    
    The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
    



    When debugging it never even gets into my create action in the controller.

    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    import com.app.model.Purchase;
    import com.app.service.IPurchaseService;
    
    @Controller
    public class PurchaseController {
    
        @Autowired
        private IPurchaseService purchaseService;
    
        @RequestMapping(value = "purchase", method = RequestMethod.GET)
        @ResponseBody
        public final List<Purchase> getAll() {
            return purchaseService.getAll();
        }
    
        @RequestMapping(value = "purchase", method = RequestMethod.POST)
        @ResponseStatus( HttpStatus.CREATED )
        public void create(@RequestBody final Purchase entity) {
            purchaseService.addPurchase(entity);
        }
    }
    



    UPDATE

    I added Jackson config to AppConfig.java:

    @Configuration
    @ComponentScan(basePackages = "com.app")
    public class AppConfig {
    
        @Bean
        public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
        {
            final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
            final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
    
            HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };
    
            String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
    
            annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
            annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
    
            return annotationMethodHandlerAdapter;
        }
    }
    



    My GETs are working correctly now:

    curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase
    
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: application/json
    Transfer-Encoding: chunked
    Date: Thu, 26 Apr 2012 21:19:55 GMT
    
    [{"id":1,"pan":111}]
    



    But I get the following when attempting a POST:

    curl -i -X POST -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"
    
    HTTP/1.1 400 Bad Request
    Server: Apache-Coyote/1.1
    Content-Type: text/html;charset=utf-8
    Content-Length: 971
    Date: Thu, 26 Apr 2012 21:29:56 GMT
    Connection: close
    
    The request sent by the client was syntactically incorrect ().
    



    My Model:

    @Entity
    @XmlRootElement
    public class Purchase implements Serializable {
    
        /**
         * 
         */
        private static final long serialVersionUID = 6603477834338392140L;
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        private Long pan;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Long getPan() {
            return pan;
        }
    
        public void setPan(Long pan) {
            this.pan = pan;
        }
    
    }
    



    Any ideas where I'm going wrong?

    Thanks

  • Thomas Buckley
    Thomas Buckley about 12 years
    Still no luck Diego, same error. See my original question with updated curl but it still shows as text/html?
  • Diego Sevilla
    Diego Sevilla about 12 years
    See my edit on the specification of the media type accepted by PurchaseController.create().
  • Thomas Buckley
    Thomas Buckley about 12 years
    Thanks sdouglass, that helped somewhat. See my update in main question for issues handling POSTs though.
  • Thomas Buckley
    Thomas Buckley about 12 years
    Thanks for the tips Diego, but adding the accepted media tytpes to the controllers is not necessary as it is handled by Spring when jackson is used.
  • Louis Hong
    Louis Hong almost 10 years
    You don't need to tell us the story of you debugging, it makes it hard to find the actual answer
  • HankCa
    HankCa over 9 years
    I think this should work though have not tested it. I'm posting a variant of this as an answer, where the MappingJackson2HttpMessageConverter() is being used in a unit test.
  • HankCa
    HankCa over 9 years
    Thanks for the Logger suggestion - that worked a treat also. The question is, why does this Spring code mask the exception and throw a 400 without any explanation?
  • Aadam
    Aadam over 7 years
    For me problem starts after adding that, 400 bad request, else it works fine, but I need to format date in response, so I have to add that property