33

I'm working on a Spring MVC project and one of the tasks I need to do requires me to have a string of JSON data sent through by the user in a POST request. I know that Spring will deserialize JSON using Jackson to objects, but if I try something like the following:

@RequestMapping(value = "/test", method = RequestMethod.POST)
public void doSomething(@RequestBody String json) {
    // do something
}

I simply get HTTP 400 Bad Request back ("The request sent by the client was syntactically incorrect.").

How can I get the raw JSON sent by the client as a string?

Madbreaks
  • 19,094
  • 7
  • 58
  • 72
Ryan Morrison
  • 593
  • 2
  • 8
  • 18
  • 1
    What is the stacktrace if any in server logs and how did you make that request , post the JS code also!!! Post the AJAX code.. – AllTooSir May 09 '13 at 16:59
  • @NoobUnChained - I'm making the request myself via a Chrome extension to test a REST API. There's no stacktrace output. @alex23 - How do I tell it to process it as a string explicitly? I can't guarantee every client will be sending a `Content-Type: application/text` header. – Ryan Morrison May 09 '13 at 18:02

6 Answers6

52

You will usually see this type of error when Spring MVC finds a request mapping that matches the URL path but the parameters (or headers or something) don't match what the handler method is expecting.

If you use the @RequestBody annotation then I believe Spring MVC is expecting to map the entire body of the POST request to an Object. I'm guessing your body is not simply a String, but some full JSON object.

If you have a java model of the JSON object you are expecting then you could replace the String parameter with that in your doSomething declaration, such as

public void doSomething(@RequestBody MyObject myobj) {

If you don't have a Java object that matches the JSON then you could try to get it working by replacing the String type with a Map<String, Object> and see if that gets you closer to a working solution.

You could also turn on debug logging in Spring MVC to get more information on why it was a bad request.

Edit: Given your requirements in the comments, you could simply inject the HttpServletRequest into your method and read the body yourself.

public void doSomething(HttpServletRequest request) {
  String jsonBody = IOUtils.toString( request.getInputStream());
  // do stuff
}
digitaljoel
  • 26,265
  • 15
  • 89
  • 115
  • You're right - the body is a full JSON object. I normally would map it to a Java object but I need to modify the raw JSON before I create the object, hence why I need the raw JSON string. – Ryan Morrison May 09 '13 at 18:05
  • Sorry to double comment, but I can't edit my above comment. I enabled debug logging in Spring and came up with this exception: `org.codehaus.jackson.map.JsonMappingException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_ARRAY token` - so how would I skip the deserializing part and just retrieve the JSON data as a string? – Ryan Morrison May 09 '13 at 18:19
  • If you want it as a string then you should send it as a String. Just because the String contains JSON content doesn't mean you need to tell Spring MVC that. Don't use @RequestBody, and send the json value as a regular request param in a form. – digitaljoel May 09 '13 at 18:44
  • This is for a REST API interfacing with middleware, the middleware will be directly calling the routes with the JSON as the body of the request. To make matters more complex, the application only accepts Content-Type values of `application/json` so even if every client could send this particular request as plaintext it would be rejected by the application (I can't change this). – Ryan Morrison May 09 '13 at 18:52
  • As a workaround you could simply define a simple class that exposes a string value. e.g. `class JsonValue { getValue(); }` – Bart May 09 '13 at 21:18
  • I updated my answer with a new possible solution. I'd be interested in how Bart's solution would work. I don't know how Jackson would map it appropriately. – digitaljoel May 09 '13 at 21:39
  • That looks like it should work perfectly, thanks @digitaljoel. – Ryan Morrison May 10 '13 at 00:37
  • I'm unaccepting this answer as it doesn't seem to work, actually. It throws this exception: `org.codehaus.jackson.map.JsonMappingException: Can not construct instance of javax.servlet.http.HttpServletRequest, problem: abstract types can only be instantiated with additional type information`. – Ryan Morrison May 10 '13 at 12:59
  • Never mind, that was my own fault, actually. It does work! Sorry about that. – Ryan Morrison May 10 '13 at 13:03
6

We had a situation where we wanted some controller methods to map the POST body to beans, and other methods where we just wanted the raw String. To accomplish this using the @RequestBody annotation, you need to configure multiple message converters, something like...

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
  <property name="useDefaultSuffixPattern" value="false"/>
</bean>

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jsonConverter" />
            <ref bean="marshallingConverter" />
            <ref bean="stringHttpMessageConverter" />
        </list>
    </property>
</bean>

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

<bean id="marshallingConverter"
      class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <constructor-arg ref="jaxb2Marshaller" />
    <property name="supportedMediaTypes" value="application/xml"/>
</bean>

<bean id="stringHttpMessageConverter"
      class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="supportedMediaTypes" value="text/plain"/>
</bean>

Then, requests to the various methods must specify the "content-type" header with an appropriate value. For those methods where the request body is mapped to a JAXB bean, specify "application/xml". And for those where the request body is a String, use "text/plain".

Logan Wayne
  • 6,001
  • 16
  • 31
  • 49
guest
  • 61
  • 1
  • 1
2

You could try avoiding @RequestBody altogether and instead grab the request body directly through a InputStream/Reader or a WebRequest/HttpServletRequest.

pimlottc
  • 3,066
  • 2
  • 29
  • 24
1

In my case is because the json has not quoted the field names. An example, this is not accepted:

{ entity: "OneEntity"} 

but this one yes:

{ "entity": "OneEntity"}

I haven't found yet how I can configure object mapping in spring context. I know there is a JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES but I don't know how set that for object mapper.

Toni Gamez
  • 6,819
  • 1
  • 23
  • 18
0

if your Content-type is "application/json" and your first messageConvertor is not org.springframework.http.converter.StringHttpMessageConverter , Spring could not work right. In my case , I did this:

<mvc:annotation-driven>
  <mvc:message-converters>
   <ref bean="stringHttpMessageConverter" /><!-- 放在前面,对@RequestBody String json 提供支持 -->
   <ref bean="mappingJacksonHttpMessageConverter" />
  </mvc:message-converters>
 </mvc:annotation-driven>


 <!-- 消息转换器 -->
 <bean id="stringHttpMessageConverter"
  class="org.springframework.http.converter.StringHttpMessageConverter">
  <property name="supportedMediaTypes">
   <list>
    <bean class="org.springframework.http.MediaType">
     <constructor-arg index="0" value="text" />
     <constructor-arg index="1" value="plain" />
     <constructor-arg index="2" value="UTF-8" />
    </bean>
    <bean class="org.springframework.http.MediaType">
     <constructor-arg index="0" value="application" />
     <constructor-arg index="1" value="json" />
     <constructor-arg index="2" value="UTF-8" />
    </bean>
   </list>
  </property>
 </bean>

 <bean id="mappingJacksonHttpMessageConverter"
  class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
  <property name="supportedMediaTypes">
   <list>
    <bean class="org.springframework.http.MediaType">
     <constructor-arg index="0" value="text" />
     <constructor-arg index="1" value="plain" />
     <constructor-arg index="2" value="UTF-8" />
    </bean>
    <bean class="org.springframework.http.MediaType">
     <constructor-arg index="0" value="application" />
     <constructor-arg index="1" value="json" />
     <constructor-arg index="2" value="UTF-8" />
    </bean>
   </list>
  </property>
  <!-- 设置时间格式, 有了这个就不用在pojo的属性上写了 -->
  <property name="objectMapper">
   <bean class="com.fasterxml.jackson.databind.ObjectMapper">
    <property name="dateFormat">
     <bean class="java.text.SimpleDateFormat">
      <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
     </bean>
    </property>
   </bean>
  </property>
 </bean>
0

For me with spring version update it was simply an " necessary now. "XXX" instead of XXX and everything is working fine as you have it. Content-Type application/json

user5101998
  • 209
  • 3
  • 9