12

I'm experimenting with the new Spring 4.0 @RestController to return a simple text response from a controller:

@RestController
@RequestMapping(value = "/heartbeat")
public class HeartbeatController {

    private static final Logger logger = LoggerFactory.getLogger(HeartbeatController.class);

    @RequestMapping
    public String heartbeat() {
        logger.info("Received heartbeat!");
        return "I'm Alive!";
    }

    @RequestMapping(value = "/test", produces = MediaType.TEXT_PLAIN_VALUE)
    public String heartbeat2() {
        logger.info("Received heartbeat!");
        return "I'm Alive!";
    }
}

When I access /heartbeat then I get back:

"I'm Alive!"

The result includes the double quotes, what I did not expect.

When I access /heartbeat/test then I get a empty response back, but I expect the I'm Alive! text.

UPDATE

curl -i http://myserver.com/rest/heartbeat

HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Server: Development/1.0 Date: Tue, 17 Dec 2013 18:59:08 GMT Cache-Control: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Length: 12

"I'm Alive!"

curl -i -H "Accept: application/json" http://myserver.com/rest/heartbeat HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Server: Development/1.0 Date: Tue, 17 Dec 2013 19:01:12 GMT Cache-Control: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Length: 12

"I'm Alive!"

curl -i http://myserver.com/rest/heartbeat/test

HTTP/1.1 406 Not Acceptable Server: Development/1.0 Date: Tue, 17 Dec 2013 19:00:13 GMT Cache-Control: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Length: 0

curl -i -H "Accept: text/plain" http://myserver.com/rest/heartbeat/test

HTTP/1.1 406 Not Acceptable Server: Development/1.0 Date: Tue, 17 Dec 2013 19:02:06 GMT Cache-Control: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Length: 0

Marcel Overdijk
  • 11,041
  • 17
  • 71
  • 110

5 Answers5

21

I found out I was missing the StringHttpMessageConverter in my WebConfig's configureMessageConverters. I was configuring the message converters to control the Jackson ObjectMapper.

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setPrettyPrint(SystemProperty.environment.value() == Development);
    mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper());
    converters.add(mappingJackson2HttpMessageConverter);
    converters.add(new StringHttpMessageConverter()); // THIS WAS MISSING
}
Marcel Overdijk
  • 11,041
  • 17
  • 71
  • 110
4

@RestController is a convenience annotation that means you no longer need to specify @ResponseBody annotation on your methods.

But it will mean that your response type is being defaulted to JSON and therefore wrapped in quotes to be properly formed.

englishteeth
  • 228
  • 2
  • 9
  • 2
    Do you mean @RestController does only support JSON responses? When I add products = MediaType.TEXT_PLAIN_VALUE I get a 406. – Marcel Overdijk Dec 16 '13 at 19:43
  • 1
    Your curl responses are curious, I haven't been able to replicate those here. heartBeat2() only giving a 406 if the request header conflicts with the producer. For the quotes, could you have jackson in your class path and/or a JSON message converter registered? – englishteeth Dec 18 '13 at 09:49
2

@RestController combines @Controller and @ResponseBody on your Controller class, as stated in the documentation.

When you annotate a method/a controller with @ResponseBody, Spring assists you with content negotiation, using the Accept HTTP request header and the produces attribute on your annotation.

In your case:

  • You get an application/json response for your heartbeat action, because your HTTP client probably asks for that Content-Type and Spring did the content negotiation.
  • You get a HTTP 406 for your hearbeat2 action, because content negotiation failed. You specified text/plain as a produces Content-Type on your Controller, whereas your HTTP client probably lists only application/json in its Accept request header.

Update: I've used the exact same curl requests but don't get the same results. Maybe a Filter or a HTTP proxy-cache is modifying HTTP headers?

Default format is text/plain:

➜ curl -v http://localhost:8080/heartbeat
> GET /heartbeat HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain;charset=ISO-8859-1
< Content-Length: 13
< Date: Wed, 18 Dec 2013 13:34:12 GMT
<
Hello, World!%

And with a produces text/plain attribute:

➜ curl -H "Accept: text/plain" -v http://localhost:8080/heartbeat/test
> GET /heartbeat/test HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: text/plain
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain
< Content-Length: 13
< Date: Wed, 18 Dec 2013 13:39:07 GMT
<
Hello, World!%

This sample application does the same thing and get good results.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • Hi Brian, thanks for your answer. See my example requests and responses. I think the first /heartbeat example adds quotes to transform it to json... I don't understand why for heartbeat/test I get 406... – Marcel Overdijk Dec 17 '13 at 19:05
  • updated my answer with new curl tests. Do you have similar results when deleting `@RestController` and adding `@ResponseBody` on methods? – Brian Clozel Dec 18 '13 at 13:50
  • thanks for your extensive answers, much appreciated. Found out that I was missing the StringHttpMessageConverter. – Marcel Overdijk Dec 18 '13 at 17:38
1

This solution worked for me. Please check the followings.

  1. Ensure your DTO is serializable and has serializable fields, getters and setters
  2. Check the dependencies for jackson. You should have
    • com.fasterxml.jackson.core:jackson-core:2.4.1
    • com.fasterxml.jackson.core:jackson-databind:2.4.1
    • com.fasterxml.jackson.core:jackson-annotations:2.4.1
    • com.fasterxml.jackson.datatype:jackson-datatype-joda:2.4.1
    • com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.4.1
  3. Fix the RequesMapping Annotation:

    @RequestMapping(value = "/test", consumes = "*/*")

  4. Check you have <mvc:annotation-driven /> directive

Fırat Küçük
  • 5,613
  • 2
  • 50
  • 53
0

This works for me:

  1. add this in maven

    com.fasterxml.jackson.core jackson-databind 2.4.4

  2. make sure <mvc:annotation-driven /> is configured in spring

cn123h
  • 2,302
  • 1
  • 21
  • 16