185

My question is essentially a follow-up to this question.

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return "Hello World";
    }
}

In the above, Spring would add "Hello World" into the response body. How can I return a String as a JSON response? I understand that I could add quotes, but that feels more like a hack.

Please provide any examples to help explain this concept.

Note: I don't want this written straight to the HTTP Response body, I want to return the String in JSON format (I'm using my Controller with RestyGWT which requires the response to be in valid JSON format).

2
  • You can return Map or any object/entity which contain your string Commented Jun 17, 2015 at 15:00
  • 1
    So you mean you want the String value to be serialized to a JSON string? Commented Jun 17, 2015 at 15:04

13 Answers 13

215

Either return text/plain (as in Return only string message from Spring MVC 3 Controller) OR wrap your String in some object

public class StringResponse {

    private String response;

    public StringResponse(String s) { 
       this.response = s;
    }

    // get/set omitted...
}

Set your response type to MediaType.APPLICATION_JSON_VALUE (= "application/json")

@RequestMapping(value = "/getString", method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE)

and you'll have a JSON that looks like

{  "response" : "your string value" }
Sign up to request clarification or add additional context in comments.

8 Comments

You could also return Collections.singletonMap("response", "your string value") to achieve the same result without having to create a wrapper class.
It's not true that it requires a key and a value. A single String or an array of strings are both valid JSON. If you disagree maybe you can explain why the jsonlint website accepts both of those as valid JSON.
how does the wrapper class get converted to a JSON?
I think it is enough to return Collections.singleton("your string value")
This does not provide a solution to the question. Problem is that the response body is Hello World but it should be JSON encoded as "Hello World", with the quotes making it a JSON string.
|
71

JSON is essentially a String in PHP or JAVA context. That means string which is valid JSON can be returned in response. Following should work.

  @RequestMapping(value="/user/addUser", method=RequestMethod.POST)
  @ResponseBody
  public String addUser(@ModelAttribute("user") User user) {

    if (user != null) {
      logger.info("Inside addIssuer, adding: " + user.toString());
    } else {
      logger.info("Inside addIssuer...");
    }
    users.put(user.getUsername(), user);
    return "{\"success\":1}";
  }

This is okay for simple string response. But for complex JSON response you should use wrapper class as described by Shaun.

4 Comments

This should be accepted answer, as this was the exact answer to the OP's question.
Curious which is the "better" position for @ResponseBody before or after the public keyword? I've always put it after, since it's more identified with the return value.
But I got this escape slashes in my response. Strange things
I don't think this is an answer to the original question. The OP stated they could add the quotes if they wanted, but that seemed hacky. This seems far worse than that. The real question is how to get Spring to recognize the String return value and convert it to the JSON representation of a plain string automatically.
32

In one project we addressed this using JSONObject (maven dependency info). We chose this because we preferred returning a simple String rather than a wrapper object. An internal helper class could easily be used instead if you don't want to add a new dependency.

Example Usage:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}

3 Comments

Maybe you should mention in your answer, that "\"Hello World\"" would work just as well w/o the extra dependendy - that is what JSONObject.quote() does, right?
I dont like the solution, but it worked for me. :-)
Or escape the String with new String(com.fasterxml.jackson.core.io.JsonStringEncoder.getInstance().quoteAsString("Hello World")) and add the quotes. (when using e.g. DropWizard). It might just be that for plain Java Strings, no special serialization happens as the assumption might be that the user is already returning a JSON string. The reverse case might be more difficult, i.e., it converts Java Strings to JSON strings but if you want to return some existing JSON (which is a String) you then wouldn't be able to anymore. But annoying behaviour and then there are no obvious helper methods...
31

You can easily return JSON with String in property response as following

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}

1 Comment

whenever you use '@RestController' ,you dont need to use '@ResponseBody'
29

Simply unregister the default StringHttpMessageConverter instance:

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
  /**
   * Unregister the default {@link StringHttpMessageConverter} as we want Strings
   * to be handled by the JSON converter.
   *
   * @param converters List of already configured converters
   * @see WebMvcConfigurationSupport#addDefaultHttpMessageConverters(List)
   */
  @Override
  protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.removeIf(c -> c instanceof StringHttpMessageConverter);
  }
}

Tested with both controller action handler methods and controller exception handlers:

@RequestMapping("/foo")
public String produceFoo() {
  return "foo";
}

@ExceptionHandler(FooApiException.class)
public String fooException(HttpServletRequest request, Throwable e) {
  return e.getMessage();
}

Final notes:

  • extendMessageConverters is available since Spring 4.1.3, if are running on a previous version you can implement the same technique using configureMessageConverters, it just takes a little bit more work.
  • This was one approach of many other possible approaches, if your application only ever returns JSON and no other content types, you are better off skipping the default converters and adding a single jackson converter. Another approach is to add the default converters but in different order so that the jackson converter is prior to the string one. This should allow controller action methods to dictate how they want String to be converted depending on the media type of the response.

5 Comments

It would be nice to have an example code regarding your 2nd final note.
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
This was the only correct answer in my opinion as the original question was how to get a simple string to be written as json, not an object or anything else which works quite easily. Thanks to this response I learned something new.
Removing the message converter can have undesired side effects. E.g. it breaks Swagger-UI with springdoc which outputs a JSON as string and relies on the coverter to unquote it. (for reference, the error message then is "Unable to render this definition The provided definition does not specify a valid version field.").
While this does work I would advise against it as you could run into problems later when adding third party libraries that add endpoints which return an already encoded json string. It would be encoded again by the Jackson message converter and produce a result you don't want.
28

Setting the return type as a Map will serialize into JSON.

@GetMapping("...")
@ResponseBody
public Map<String, Object> endpointExample(...) {
    Map<String, Object> rtn = new LinkedHashMap<>();

    rtn.put("pic", image);
    rtn.put("potato", "King Potato");

    return rtn;

}

This will return:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449", "potato":"King Potato"}

Comments

11

Make simple:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }

1 Comment

Using an ResponseEntity seems to be state of the art to me. +1
8

Add produces = "application/json" in @RequestMapping annotation like:

@RequestMapping(value = "api/login", method = RequestMethod.GET, produces = "application/json")

Hint: As a return value, i recommend to use ResponseEntity<List<T>> type. Because the produced data in JSON body need to be an array or an object according to its specifications, rather than a single simple string. It may causes problems sometimes (e.g. Observables in Angular2).

Difference:

returned String as json: "example"

returned List<String> as json: ["example"]

1 Comment

@GetMapping(value = "/version") public ResponseEntity<?> version() { return ResponseEntity.ok(VERSION); } doesn't work, still sends plain string instead of json.
2

Add @ResponseBody annotation, which will write return data in output stream.

1 Comment

this didn't work for me. I have @PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
1

This issue has driven me mad: Spring is such a potent tool and yet, such a simple thing as writing an output String as JSON seems impossible without ugly hacks.

My solution (in Kotlin) that I find the least intrusive and most transparent is to use a controller advice and check whether the request went to a particular set of endpoints (REST API typically since we most often want to return ALL answers from here as JSON and not make specializations in the frontend based on whether the returned data is a plain string ("Don't do JSON deserialization!") or something else ("Do JSON deserialization!")). The positive aspect of this is that the controller remains the same and without hacks.

The supports method makes sure that all requests that were handled by the StringHttpMessageConverter(e.g. the converter that handles the output of all controllers that return plain strings) are processed and in the beforeBodyWrite method, we control in which cases we want to interrupt and convert the output to JSON (and modify headers accordingly).

@ControllerAdvice
class StringToJsonAdvice(val ob: ObjectMapper) : ResponseBodyAdvice<Any?> {
    
    override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean =
        converterType === StringHttpMessageConverter::class.java

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class<out HttpMessageConverter<*>>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ): Any? {
        return if (request.uri.path.contains("api")) {
            response.getHeaders().contentType = MediaType.APPLICATION_JSON
            ob.writeValueAsString(body)
        } else body
    }
}

I hope in the future that we will get a simple annotation in which we can override which HttpMessageConverter should be used for the output.

1 Comment

Nice. I think this is the best way if you have multiple endpoints which return a string of which some are already in json (most likely from 3rd party libraries).
1

Simple and Straightforward send any object or return simple List

@GetMapping("/response2")
    @ResponseStatus(HttpStatus.CONFLICT)
    @ResponseBody List<String> Response2() {
        List<String> response = new ArrayList<>(Arrays.asList("Response2"));
        
        return response;
        
    }

I have added HttpStatus.CONFLICT as Random response to show how to pass RequestBody also the HttpStatus

{Postman Response

Comments

1

One way to make it work for a single controller is to wrap it as a TextNode:

@RestController
public class TestController {

    @RequestMapping("/get-json-string")
    public TextNode getStringAsValidJson() {

        return TextNode.valueOf("Hello world!");

    }
}

The TextNode won't be handled by the StringHttpMessageConverter, but will be passed through to the MappingJackson2HttpMessageConverter.

1 Comment

Best reply. Does what was asked for (unlike almost every other response) without additional dependencies. No need to complicate the answer with values though, just do a return TextNode.valueOf("Hello world!");.
0

Annotate your method with the @ResponseBody annotation to tell spring you are not trying to render a view and simple return the string plain

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.