4

I want to send JSON from my controller. I have the following configuration.

spring-servlet.xml :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven/>

    <mvc:resources mapping="/resources/**" location="/resources/"/>
    <context:component-scan base-package="com.castle.controllers"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

.js :

function testAjax() {
    var data = {userName: "MyUsername", password:"Password"};
    $.ajax({
        url: 'ajax/test.htm',
        dataType : 'json',
        type : 'POST',
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function(response){
            alert('Load was performed.');
        }
    });
}

UserTest.java:

public class UserTest {
    private String userName;
    private String password;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

TestAjaxController.java :

@Controller
@RequestMapping("/ajax")
public class TestAjaxController {

    @RequestMapping(method = RequestMethod.POST, value = "/test.htm")
    public @ResponseBody
    UserTest testAjaxRequest(@RequestBody UserTest user) {
        return user;
    }
}

pom.xml :

    <dependency>
                <groupId>org.codehaus.jackson</groupId>
                <artifactId>jackson-mapper-asl</artifactId>
                <version>1.9.13</version>
            </dependency>
            <dependency>
                <groupId>org.codehaus.jackson</groupId>
                <artifactId>jackson-core-asl</artifactId>
                <version>1.9.13</version>
</dependency>

When i do this request, i get in my Controller JSON represented as UserTest object. But on return :

HTTP Status 406 - The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

What i'm doing wrong? I know, there is a lot of questions about such cases, but i can't fix it for 2 days...

UPDATE I Have found the solution!! It's only need to return an Object. Not a User object or something. But return Object;

 public @ResponseBody Object testAjaxRequest(@RequestBody UserTest user) {
        List<UserTest> list = new ArrayList<>();
        list.add(user);
        list.add(user);
        list.add(user);
        return list;
Shatranaft
  • 119
  • 2
  • 3
  • 8

5 Answers5

21

The main issue here is that the path "/test.htm" is going to use content negotiation first before checking the value of an Accept header. With an extension like *.htm, Spring will use a org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy and resolve that the acceptable media type to return is text/html which does not match what MappingJacksonHttpMessageConverter produces, ie. application/json and therefore a 406 is returned.

The simple solution is to change the path to something like /test, in which content negotiation based on the path won't resolve any content type for the response. Instead, a different ContentNegotiationStrategy based on headers will resolve the value of the Accept header.

The complicated solution is to change the order of the ContentNegotiationStrategy objects registered with the RequestResponseBodyMethodProcessor which handles your @ResponseBody.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
1

I had the same problem in the end it was the version of org.codehaus.jackson 1.9.x, when I switched from 1.9.x jackson to 2.x (fasterxml) in my pom.xml

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.2.3</version>

</dependency>

also is necesary : <mvc:annotation-driven />

ramird23
  • 77
  • 1
  • 2
1

I ran into this issue when upgrading Spring in a legacy project. The .html suffix of the AJAX endpoints (which were trying to return JSON) were indeed forcibly made to try to produce HTML due to the suffix, and since the handlers were returning an object, the request ended in a 406 error since Spring couldn't figure out how to make HTML out of a plain Java object.

Instead of altering the endpoint suffixes, or doing a complete configuration of a custom ContentNegotiationStrategy, making this change was enough:

<mvc:annotation-driven />

changed to:

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false" />
</bean>
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
jlaitio
  • 1,878
  • 12
  • 13
  • 1
    I was about to go insane. First time i read about ContentNegotiation. I had the same problem, legacy app with .html endings sending out json. It's crazy. You pointed me in the right direction and finally solved my issue. Whenever you are in Germany, ping me and you'll have access to an infinite beer pipeline. This blog also helped me, after knowing the terms: https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc – Christian Oct 13 '17 at 20:15
0

Adding these lines to context configuration solved the same issue for me:

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

I used Spring 4.1.x and Jackson 2.4.2

zjor
  • 994
  • 2
  • 12
  • 22
-2

Make sure to use produces = "application/json" in your annotations.

Marty
  • 174
  • 1
  • 9
  • 1
    As I've explained in a comment to Pracede's answer, these `@RequestMapping` attribute values only restrict what the handler method is mapped to. By not specifying that value, you are already covering it, so it isn't explicitly necessary in this case. – Sotirios Delimanolis Jan 20 '14 at 15:58