Keywords: Spring Boot | JSON Serialization | REST API | Jackson | @RestController
Abstract: This article provides an in-depth analysis of the 'No converter found' exception encountered when returning JSON objects in Spring Boot applications. By comparing different JSON library usage patterns, it explains the working mechanism of Jackson's automatic serialization and offers practical code examples using POJO, Map, and ResponseEntity solutions. The paper also explores the underlying mechanisms of @RestController annotation and best practices to help developers avoid common configuration errors.
Problem Background and Exception Analysis
During Spring Boot application development, developers frequently encounter JSON serialization-related configuration issues. A typical scenario occurs when using org.json.JSONObject as the return type of a REST controller, resulting in the system throwing java.lang.IllegalArgumentException: No converter found for return value of type: class org.json.JSONObject exception.
The root cause of this exception lies in Spring Boot's default use of the Jackson library for JSON serialization, which cannot directly handle objects of type org.json.JSONObject. Spring Boot's auto-configuration mechanism detects the @RestController annotation and attempts to use configured HTTP message converters to process return values.
JSON Processing Mechanism in Spring Boot
Spring Boot automatically includes the Jackson library through the spring-boot-starter-web dependency, which handles object serialization and deserialization. When a method returns an object, Spring follows this processing flow:
- Detect method return type
- Find appropriate HTTP message converters
- Use the found converter to transform the object into HTTP response body
The @RestController annotation is essentially a combination of @Controller and @ResponseBody. This means all methods in the controller automatically apply @ResponseBody semantics, where return values are written directly to the HTTP response body rather than being resolved as view names.
Solution 1: Using POJO Objects
The most recommended approach is using Plain Old Java Objects (POJO) as return values. Jackson can automatically serialize POJO objects with appropriate getter methods.
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public HelloResponse sayHello() {
return new HelloResponse("bb");
}
public static class HelloResponse {
private String aa;
public HelloResponse(String aa) {
this.aa = aa;
}
public String getAa() {
return aa;
}
public void setAa(String aa) {
this.aa = aa;
}
}
}
This approach offers benefits of type safety, easy testing, and maintainability. Jackson automatically serializes the object into JSON format: {"aa":"bb"}.
Solution 2: Using Map Structures
For scenarios requiring dynamic JSON structure construction, Map can be used as the return type.
@GetMapping("/hello")
public Map<String, Object> sayHello() {
Map<String, Object> response = new HashMap<>();
response.put("aa", "bb");
response.put("timestamp", System.currentTimeMillis());
return response;
}
This method provides high flexibility, suitable for returning data with non-fixed structures. Jackson can properly handle serialization of Map types.
Solution 3: Using Jackson's ObjectNode
For more complex JSON operations, Jackson's ObjectNode can be used directly.
@RestController
@RequestMapping("/api")
public class HelloController {
@Autowired
private ObjectMapper objectMapper;
@GetMapping("/hello")
public ObjectNode sayHello() {
ObjectNode node = objectMapper.createObjectNode();
node.put("aa", "bb");
node.put("number", 42);
node.put("active", true);
return node;
}
}
This approach offers the most comprehensive JSON operation capabilities, supporting complex structures like nested objects and arrays.
Solution 4: Using ResponseEntity
For scenarios requiring control over HTTP status codes and response headers, ResponseEntity can be used.
@GetMapping(path = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> sayHello() {
Map<String, String> response = new HashMap<>();
response.put("aa", "bb");
return new ResponseEntity<>(response, HttpStatus.OK);
}
This method allows precise control over various aspects of HTTP responses, including status codes, response headers, and content types.
Dependency Configuration Verification
Ensure the project's pom.xml file contains necessary dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This dependency automatically includes Jackson-related libraries, including jackson-databind, jackson-core, and jackson-annotations.
Best Practice Recommendations
Based on practical development experience, we recommend the following best practices:
- Prefer POJO Usage: For fixed data structures, using POJO provides better type safety and IDE support
- Use Map Appropriately: For dynamic structures or prototype development, Map offers sufficient flexibility
- Avoid Direct JSONObject Returns: Unless specifically required, avoid using third-party JSON library objects as return values
- Explicitly Specify Produces Attribute: Clearly specify
produces = MediaType.APPLICATION_JSON_VALUEin@RequestMappingor@GetMapping
Conclusion
Spring Boot provides powerful JSON serialization support, but requires proper understanding of its working mechanisms. By using appropriate return types and following best practices, common configuration issues can be avoided, enabling the construction of stable and reliable REST APIs. Remember, Spring Boot's design philosophy favors convention over configuration, and understanding these conventions can significantly improve development efficiency.