Deep Dive into ObjectMapper: From Internal Mechanics to Exception Handling
try {
objectMapper.readValue(request, A::class.java)
} catch (e: JsonProcessingException) {
throw CoreException(InMemoryExceptionCode.FAILED_PARSE_JSON)
} catch (e: JsonMappingException) {
throw CoreException(InMemoryExceptionCode.FAILED_MAP_TO_SCHEMA)
}
As a web backend developer, you quickly realize how frequently ObjectMapper is used. In a typical Controller, the Jackson library's ObjectMapper handles the serialization of JSON requests into our desired object types. It is also common practice for developers to inject ObjectMapper to handle data when interacting with Redis.
To be honest, I didn't find the code above strange at first. In fact, based on my experience, I thought writing it this way was the only way to perfectly control the frequent exceptions thrown by ObjectMapper. This assumption likely stemmed from not looking deeply enough into the internal libraries that Spring relies on.
How ObjectMapper Operates in a Controller
Identify Content-Type: When an HTTP request arrives, the server checks the
Content-Type.Trigger Jackson: If it is
application/json, Jackson’sObjectMapperis invoked.Deserialization:
ObjectMapperconverts the JSON string into an instance of the specified class.
Serialization > - The process of converting an object in memory into a format that can be stored or transmitted.
- Converting Java/Kotlin objects into JSON strings or byte streams.
Deserialization > - The process of converting stored or transmitted data back into an object that can be used in memory.
- Converting JSON strings back into Java/Kotlin objects.
A quick question: Does ObjectMapper also work when communicating with a Database?
No.
ObjectMapperis primarily responsible for converting JSON objects during HTTP communication.When communicating with a DB, JPA maps objects (Entities) based on table metadata, while MyBatis maps SQL results to objects.
So far, we know ObjectMapper deserializes JSON strings into objects. However, data actually arrives at the server as a byte stream, not a raw JSON string. Let’s look at where that transformation happens.
Client sends JSON:
{"name": "Kim", "age": 25}Network Transmission:
The HTTP request is converted into a byte stream.
Content-Type: application/jsonis included in the header.Spring Server:
Byte stream → Converted to JSON string.
ObjectMapperconverts the JSON string → Kotlin/Java object.
Who Converts Byte Streams to JSON?
In Spring MVC, the HttpMessageConverter is responsible for converting the HTTP request byte stream into a JSON string.
Arrival: The HTTP request byte stream arrives.
Read: The byte stream is read via
ServletInputStream.Process:
MappingJackson2HttpMessageConvertertakes over.It uses
ObjectMapperinternally.It converts the byte stream into a string using
InputStreamReader.
Convert:
ObjectMapperconverts the JSON string into an object.
Looking at Spring's default configuration, we can see where converters are added in the WebMvc configuration classes.
// Inside WebMvcConfigurationSupport
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
// ...
}
If you examine MappingJackson2HttpMessageConverter, it inherits from AbstractJackson2HttpMessageConverter. Inside that class, you can see the part where it receives the byte stream through an InputStream object.
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
// ...
try {
InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
// ...
End-to-End Flow Summary
Initial HTTP Processing: Request arrives → Tomcat Connector assigns a thread → Request parsed into
HttpServletRequest.FilterChain: Passes through
DelegateFilter→SecurityFilter→ etc.DispatcherServlet:
doDispatch()is called → Find Handler viaHandlerMapping→ Execute viaHandlerAdapter.@RequestBody Processing: Read body via
ServletInputStream→HttpMessageConverter(Byte Stream → JSON → Object).@ResponseBody Processing: Return value processed by
HttpMessageConverter(Object → JSON → Byte Stream) → Response written viaServletOutputStream.
Moving Beyond Try-Catch: ObjectMapper Configurations
Let's analyze the exceptions handled in the original code:
JsonProcessingException: The root exception for Jackson. It covers all issues during JSON parsing or generation (syntax errors, incomplete strings). Since these are often "human errors" from the client side, we might still need some level of control here.JsonMappingException: A subclass ofJsonProcessingExceptionfor specific mapping issues (type mismatch, missing fields).- Realization: My original code caught
JsonProcessingExceptionfirst. Since it's the parent,JsonMappingExceptionwould never be caught in its own block. I should have analyzed the library hierarchy more carefully.
- Realization: My original code caught
Recommended ObjectMapper Configurations
Instead of messy try-catches, we can configure ObjectMapper to handle many common issues gracefully.
1. Basic & Deserialization Settings
objectMapper.apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL) // Exclude nulls
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // Ignore unknown fields
configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true) // Unknown Enum as null
registerModule(JavaTimeModule()) // Support Java 8 Date/Time
}
2. Custom Wrapper for Clean Code
Since we cannot completely eliminate JsonProcessingException, I recommend using a Wrapper class:
@Component
@Slf4j
class JsonConverter(private val objectMapper: ObjectMapper) {
fun <T> fromJson(json: String, type: Class<T>): Optional<T> {
return try {
Optional.ofNullable(objectMapper.readValue(json, type))
} catch (e: JsonProcessingException) {
log.error("JSON Conversion Failed: {}", e.getMessage())
Optional.empty()
}
}
}
Supplemental: Issues with MappingJackson2HttpMessageConverter
I’d like to share an issue discussed in my dev community regarding MappingJackson2HttpMessageConverter.
The Problem: When communicating with an external partner API using WebClient or RestClient, an error occurred stating the request body was empty. Interestingly, it worked fine with OpenFeign or when sending data as a raw String.
The Cause: Chunked Transfer Encoding. When you pass an Object directly to the request body, MappingJackson2HttpMessageConverter triggers. In Spring 6.1+, to optimize memory, RestTemplate and RestClient no longer buffer the request body by default. Consequently, the Content-Length header is not set, and the data is sent using Transfer-Encoding: chunked.
If the external partner's server does not support chunked encoding, it fails.
Solutions:
Override
getContentLengthinMappingJackson2HttpMessageConverter.Wrap the
ClientHttpRequestFactorywithBufferingClientHttpRequestFactoryto force buffering (and thus setContent-Length).Send the data as a
String(which usesStringHttpMessageConverterthat provides aContent-Length).
This change was documented in the Spring Framework 6.1 Release Notes to optimize memory usage. It’s a crucial reminder that keeping up with release notes is just as important as writing clean code!

