142

I have a sample RestController in Spring Boot:

@RestController
@RequestMapping("/api")
class MyRestController
{
    @GetMapping(path = "/hello")
    public JSONObject sayHello()
    {
        return new JSONObject("{'aa':'bb'}");
    }
}

I am using the JSON library org.json

When I hit API /hello, I get an exception saying :

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: No converter found for return value of type: class org.json.JSONObject] with root cause

java.lang.IllegalArgumentException: No converter found for return value of type: class org.json.JSONObject

What is the issue? Can someone explain what exactly is happening?

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
iwekesi
  • 2,228
  • 4
  • 19
  • 29
  • Jackson cannot convert JSONObject to json. – Pau Jun 30 '17 at 06:16
  • Ok, I understand that. What can be done to fix this? – iwekesi Jun 30 '17 at 06:16
  • 1
    I want the response to be constructed on the fly. I don't want to create specific classes for each response. – iwekesi Jun 30 '17 at 06:18
  • 2
    It might be better to just have your method return as String. Additionally, you can also append the annotation @ResponseBody to the method, this will handle your response as requested :-) `@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}` – vegaasen Jun 30 '17 at 06:19
  • @vegaasen can you elaborate a bit about ResponseBody – iwekesi Jun 30 '17 at 06:23
  • For anyone looking for mapping dynamic objects this was helpful https://www.baeldung.com/jackson-mapping-dynamic-object – iwekesi Jul 14 '20 at 17:41

10 Answers10

164

As you are using Spring Boot web, Jackson dependency is implicit and we do not have to define explicitly. You can check for Jackson dependency in your pom.xml in the dependency hierarchy tab if using eclipse.

And as you have annotated with @RestController there is no need to do explicit json conversion. Just return a POJO and jackson serializer will take care of converting to json. It is equivalent to using @ResponseBody when used with @Controller. Rather than placing @ResponseBody on every controller method we place @RestController instead of vanilla @Controller and @ResponseBody by default is applied on all resources in that controller.
Refer this link: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

The problem you are facing is because the returned object(JSONObject) does not have getter for certain properties. And your intention is not to serialize this JSONObject but instead to serialize a POJO. So just return the POJO.
Refer this link: https://stackoverflow.com/a/35822500/5039001

If you want to return a json serialized string then just return the string. Spring will use StringHttpMessageConverter instead of JSON converter in this case.

Vy Do
  • 46,709
  • 59
  • 215
  • 313
prem kumar
  • 5,641
  • 3
  • 24
  • 36
  • if json string is what you want to return from java then you can just return a string if it is already json serialized. No need to convert string to json and json back to string. – prem kumar Jun 30 '17 at 09:17
  • 17
    If you want to return a set of name-value pairs without a rigid compile-time structure, you could return a `Map` or a `Properties` object – Vihung Sep 08 '17 at 15:26
  • @prem kumar random question : what do you mean with 'instead of vanilla Controller and ResponseBody'? what s vanilla here? – Orkun Mar 01 '18 at 15:28
  • i meant a usual Controller and with ResponseBody annotation placed on every request method. – prem kumar Mar 01 '18 at 15:37
  • In addition to what @premkumar said, you can check out this reference link https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.message-converters, Spring MVC uses the HttpMessageConverter interface to convert HTTP requests and responses using the Jackson library by default. – Cletus Ajibade Feb 07 '22 at 01:12
124

The reason why your current approach doesn't work is because Jackson is used by default to serialize and to deserialize objects. However, it doesn't know how to serialize the JSONObject. If you want to create a dynamic JSON structure, you can use a Map, for example:

@GetMapping
public Map<String, String> sayHello() {
    HashMap<String, String> map = new HashMap<>();
    map.put("key", "value");
    map.put("foo", "bar");
    map.put("aa", "bb");
    return map;
}

This will lead to the following JSON response:

{ "key": "value", "foo": "bar", "aa": "bb" }

This is a bit limited, since it may become a bit more difficult to add child objects. Jackson has its own mechanism though, using ObjectNode and ArrayNode. To use it, you have to autowire ObjectMapper in your service/controller. Then you can use:

@GetMapping
public ObjectNode sayHello() {
    ObjectNode objectNode = mapper.createObjectNode();
    objectNode.put("key", "value");
    objectNode.put("foo", "bar");
    objectNode.put("number", 42);
    return objectNode;
}

This approach allows you to add child objects, arrays, and use all various types.

g00glen00b
  • 41,995
  • 13
  • 95
  • 133
70

You can either return a response as String as suggested by @vagaasen or you can use ResponseEntity Object provided by Spring as below. By this way you can also return Http status code which is more helpful in webservice call.

@RestController
@RequestMapping("/api")
public class MyRestController
{

    @GetMapping(path = "/hello", produces=MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> sayHello()
    {
         //Get data from service layer into entityList.

        List<JSONObject> entities = new ArrayList<JSONObject>();
        for (Entity n : entityList) {
            JSONObject entity = new JSONObject();
            entity.put("aa", "bb");
            entities.add(entity);
        }
        return new ResponseEntity<Object>(entities, HttpStatus.OK);
    }
}
Sangam Belose
  • 4,262
  • 8
  • 26
  • 48
  • 2
    If I add JSONObject in entities, it is again giving me similar exception – iwekesi Jun 30 '17 at 09:13
  • @Sangam your answer helped me for another problem related to jackson-dataformat-xml – divine Nov 08 '17 at 13:06
  • This was a big help! Thank you! – jones-chris Feb 07 '18 at 01:13
  • 1
    I wonder why this answer isn't upvoted more. I'm new to Spring too, so I don't know if this is a good software engineering practice. With that said, this answer really helped me. However, I did have a lot of trouble with a `JSONObject`, but since Spring uses Jackson I changed it to a `HashMap` instead and then the code that I read in this answer did work. – Melvin Roest Aug 14 '18 at 21:06
  • 3
    +1 for suggesting the MediaType.APPLICATION_JSON_VALUE as it ensures that produced result get parsed as json not xml as may happen if you don't define – Sandeep Mandori Sep 04 '18 at 03:30
  • 1
    Where did you declare your entityList? – xmlParser Oct 04 '18 at 08:57
  • @xmlParser its assumed to be received from service layer. Here we dont do any IDE validation whether you have declared the list and all those things. Its considered by default.Please read the question carefully. Understand the question carefully before downvoting. – Sangam Belose Oct 04 '18 at 09:17
  • @SangamBelose nvm I have done it manually... Changed the vote too. – xmlParser Oct 04 '18 at 09:25
  • it is returning this for me: [ { "empty": false } ] – sak Oct 29 '20 at 06:57
  • @sak you need to read the data into entityList from some source. – Sangam Belose Oct 29 '20 at 09:46
  • what source? can you please name any? – sak Oct 30 '20 at 04:22
  • @sak The source can be anything, may be service layer getting data from database or any file storage. – Sangam Belose Oct 30 '20 at 05:14
21

you can also use a hashmap for this

@GetMapping
public Map<String, Object> get() {
    Map<String, Object> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("results", somePOJO);
    return map;
}
riddle_me_this
  • 8,575
  • 10
  • 55
  • 80
mchouhan_google
  • 1,775
  • 1
  • 20
  • 28
10

More correct create DTO for API queries, for example entityDTO:

  1. Default response OK with list of entities:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

But if you need return different Map parameters you can use next two examples
2. For return one parameter like map:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. And if you need return map of some parameters(since Java 9):
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getSomeParameters() {
    return ResponseEntity.status(HttpStatus.OK).body(Map.of(
            "key-1", "value-1",
            "key-2", "value-2",
            "key-3", "value-3"));
}
Olexander Yushko
  • 2,434
  • 16
  • 16
7
@RequestMapping("/api/status")
public Map doSomething()
{
    return Collections.singletonMap("status", myService.doSomething());
}

PS. Works only for 1 value

Dmitry
  • 557
  • 5
  • 12
5

If you need to return a JSON object using a String, then the following should work:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
...

@RestController
@RequestMapping("/student")
public class StudentController {

    @GetMapping
    @RequestMapping("/")
    public ResponseEntity<JsonNode> get() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode json = mapper.readTree("{\"id\": \"132\", \"name\": \"Alice\"}");
        return ResponseEntity.ok(json);
    }
    ...
}
Ashwin
  • 7,277
  • 1
  • 48
  • 70
4

use ResponseEntity<ResponseBean>

Here you can use ResponseBean or Any java bean as you like to return your api response and it is the best practice. I have used Enum for response. it will return status code and status message of API.

@GetMapping(path = "/login")
public ResponseEntity<ServiceStatus> restApiExample(HttpServletRequest request,
            HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        loginService.login(username, password, request);
        return new ResponseEntity<ServiceStatus>(ServiceStatus.LOGIN_SUCCESS,
                HttpStatus.ACCEPTED);
    }

for response ServiceStatus or(ResponseBody)

    public enum ServiceStatus {

    LOGIN_SUCCESS(0, "Login success"),

    private final int id;
    private final String message;

    //Enum constructor
    ServiceStatus(int id, String message) {
        this.id = id;
        this.message = message;
    }

    public int getId() {
        return id;
    }

    public String getMessage() {
        return message;
    }
}

Spring REST API should have below key in response

  1. Status Code
  2. Message

you will get final response below

{

   "StatusCode" : "0",

   "Message":"Login success"

}

you can use ResponseBody(java POJO, ENUM,etc..) as per your requirement.

Onic Team
  • 1,620
  • 5
  • 26
  • 37
4

I use to return Map<String,Object> in the Controller by using the toMap() method of org.json.JSONObject as follows.

@GetMapping("/json")
public Map<String, Object> getJsonOutput() {       
    JSONObject jsonObject = new JSONObject();
    //construct jsonObject here
    return jsonObject.toMap();
}
Shehan Simen
  • 1,046
  • 1
  • 17
  • 28
1

you can do this :

@RestController
@RequestMapping("/api")
class MyRestController
{
    @GetMapping(path = "/hello")
    public JSONObject sayHello()
    {
        return new JSONObject("{'aa':'bb'}").toMap();;
    }
}
David KELLER
  • 464
  • 4
  • 8