16

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
  • 5,836
  • 19
  • 62
  • 110
  • I think it has something to do with your headers. You are accepting application/json with curl, yet it is being recognized as text/html mimetype. – wachpwnski Apr 25 '12 at 22:16

9 Answers9

16

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.

Community
  • 1
  • 1
Thomas Buckley
  • 5,836
  • 19
  • 62
  • 110
  • 4
    You don't need to tell us the story of you debugging, it makes it hard to find the actual answer – Louis Hong Jul 10 '14 at 09:36
  • 1
    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? – HankCa Oct 22 '14 at 06:16
6

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

Community
  • 1
  • 1
sdouglass
  • 2,350
  • 16
  • 25
5

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();
        }
    }
yoram givon
  • 612
  • 7
  • 7
  • 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 Oct 22 '14 at 03:10
2

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.

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
2

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;
    }
    
SagarPPanchal
  • 9,839
  • 6
  • 34
  • 62
Nisarg Panchal
  • 131
  • 1
  • 6
2

Here is a unit test solution similar to yoram givon's answer - https://stackoverflow.com/a/22516235/1019307.

public class JSONFormatTest
{
    MockMvc mockMvc;

    // The controller used doesn't seem to be important though YMMV
    @InjectMocks
    ActivityController controller;  

    @Before
    public void setup()
    {
        MockitoAnnotations.initMocks(this);

        this.mockMvc = standaloneSetup(controller).setMessageConverters(new MappingJackson2HttpMessageConverter())
                .build();
    }

    @Test
    public void thatSaveNewDataCollectionUsesHttpCreated() throws Exception
    {
        String jsonContent = getHereJSON02();
        this.mockMvc
                .perform(
                     post("/data_collections").content(jsonContent).contentType(MediaType.APPLICATION_JSON)
                                .accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isCreated());
    }

    private String getHereJSON01()
    {
        return "{\"dataCollectionId\":0,\"name\":\"Sat_016\",\"type\":\"httpUploadedFiles\"," ...
    }
}

Run the unit test and the print() should print out the MockHttpServletRequest including the Exception.

In Eclipse (not sure about how to do this in other IDEs), click on the Exception link and a properties dialog for that exception should open. Tick the 'enabled' box to break on that exception.

Debug the unit test and Eclipse will break on the exception. Inspecting it should reveal the problem. In my case it was because I had two of the same entity in my JSON.

Community
  • 1
  • 1
HankCa
  • 9,129
  • 8
  • 62
  • 83
0

I experienced once and finally solved it by adding the jar file jackson-mapper-asl.jar. Go check if you have included all these dependencies although the exception itself does not tell u that.

And you really don't need to explicitly configure the bean, and you don't need to put "consumes" in @RequestMapping statement. I'm using Spring 3.1 btw.

contentType : "application/json" is the only one you need to configure. yes, client side.

tonywu
  • 1
0

Try to add the following code in your app configuration

<mvc:annotation-driven>
  <mvc:message-converters>
      <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
          <property name="objectMapper" ref="jacksonObjectMapper" />
      </bean>
  </mvc:message-converters>

Roberto
  • 4,524
  • 1
  • 38
  • 30
  • 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 – Aadam Oct 17 '16 at 04:02
0

I had the same problem and I resolved it.

1 add MappingJackson2HttpMessageConverter as described in that thread (see also section 4 http://www.baeldung.com/spring-httpmessageconverter-rest)

2 use correct command (with escape symbols):

curl -i -X POST -H "Content-Type:application/json" -d "{\"id\":\"id1\",\"password\":\"password1\"}" http://localhost:8080/user