Jackson ObjectMapper Basics with the Kotlin Module
One ObjectMapper plus jackson-module-kotlin turns your data classes into JSON and back, no boilerplate constructors required.
Explore the full explanation 8 ideas
Jackson is the JSON engine that Spring Boot uses under the hood, and its central type is the ObjectMapper. An ObjectMapper is a configurable, thread-safe object that knows how to serialize Java/Kotlin objects into JSON text and deserialize JSON text back into objects. The two methods you will reach for constantly are writeValueAsString(value), which returns a JSON String, and readValue(json, Type), which parses a String (or stream) back into a typed instance. Because a configured mapper is thread-safe and relatively expensive to build, the idiomatic pattern is to create one instance and reuse it everywhere rather than constructing a fresh mapper per request.
Plain Jackson was designed for Java, and that creates friction with Kotlin. Out of the box, Jackson deserializes by calling a no-argument constructor and then setting fields one by one. Kotlin data classes typically have no no-arg constructor; they declare their properties as constructor parameters, often with val (immutable) and non-nullable types. If you point a vanilla ObjectMapper at a data class, you will hit errors like 'Cannot construct instance ... no Creators, like default constructor, exist', or you will silently lose Kotlin's null-safety guarantees because Jackson is happy to leave a non-null property null.
The fix is the jackson-module-kotlin dependency. Add it to your build (for example, com.fasterxml.jackson.module:jackson-module-kotlin on the classpath via Gradle) and register it on your mapper. This module teaches Jackson to read Kotlin's metadata: it can call the primary constructor with parameter names, it respects default parameter values when a JSON field is absent, and it honors nullability so that a missing or null JSON value for a non-nullable property fails fast with a MismatchedInputException (historically a MissingKotlinParameterException) instead of producing a corrupt object. In short, it makes idiomatic, immutable data classes 'just work' as your serialization model.
There are two common ways to wire it up. The terse, Kotlin-friendly way is the jacksonObjectMapper() factory function, which returns a ready-to-use ObjectMapper with the Kotlin module already registered. The explicit way is to start from a builder or a plain ObjectMapper and call registerKotlinModule() yourself: ObjectMapper().registerKotlinModule(). Both produce an equivalent result; jacksonObjectMapper() is simply a convenience wrapper. Use registerKotlinModule() when you are customizing a mapper you obtained from somewhere else and want to add Kotlin support to it.
Most real applications register more than one module. A typical setup chains registerKotlinModule() with JavaTimeModule (the jackson-datatype-jsr310 module) so that java.time types like Instant and LocalDate serialize as ISO-8601 strings instead of numeric timestamps. You will also frequently configure how unknown fields are handled: by default Jackson throws when it sees a JSON property that has no matching field, but calling configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) makes the mapper tolerant of extra fields, which is valuable when you consume third-party APIs that add properties over time.
A subtle but important detail in Kotlin is reified type information for generic targets. Jackson needs the full type, including type parameters, to deserialize collections and generics correctly; readValue(json, List::class.java) would erase the element type. The jackson-module-kotlin library ships an inline reified extension, readValue<T>(json), that captures the complete generic type for you. So mapper.readValue<List<User>>(jsonArray) round-trips a list of User objects with the element type preserved, which is exactly what you want and far cleaner than building a TypeReference by hand.
In a Spring Boot 3 application you usually do not create the mapper yourself for controllers and @RequestBody binding, because Spring Boot auto-configures a Jackson ObjectMapper bean and, when jackson-module-kotlin is on the classpath, registers the Kotlin module automatically. Your job is mainly to add the dependency and, if you need tweaks, expose a Jackson2ObjectMapperBuilderCustomizer or set spring.jackson.* properties. You still construct mappers explicitly for non-HTTP work: writing to Kafka, persisting JSON columns, calling external services with a WebClient body codec, or any place you serialize by hand.
Two practices keep Jackson code healthy. First, treat the ObjectMapper as a shared, immutable-after-configuration singleton; configure it once at startup and inject or reference that single instance. Second, model your JSON with explicit data classes and let nullability express optionality: a non-nullable property means the field is required, a nullable property (or one with a default) means it is optional. When you combine reusable mappers, the Kotlin module, and well-typed data classes, serialization becomes nearly invisible, boilerplate-free plumbing, and your domain types stay clean and idiomatic.
Let’s figure it out.
Ask me about this course or where to start.
import com.fasterxml.jackson.databind.ObjectMapperimport com.fasterxml.jackson.module.kotlin.registerKotlinModuleimport com.fasterxml.jackson.module.kotlin.readValuedata class User(val id: Long, val name: String, val active: Boolean = true)// Configure once, reuse everywhere (thread-safe after setup).val mapper: ObjectMapper = ObjectMapper().registerKotlinModule()fun main() {val user = User(id = 1, name = "Ada")// Serialize: object -> JSON Stringval json = mapper.writeValueAsString(user)println(json) // {"id":1,"name":"Ada","active":true}// Deserialize: JSON String -> typed objectval parsed: User = mapper.readValue(json)println(parsed) // User(id=1, name=Ada, active=true)}
Build a reusable ObjectMapper for Kotlin and round-trip a data class. registerKotlinModule() lets Jackson use the primary constructor of an immutable data class with no no-arg constructor.
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapperimport com.fasterxml.jackson.module.kotlin.readValuedata class Product(val sku: String, val price: Double)val mapper = jacksonObjectMapper()fun main() {val jsonArray = """[{"sku":"A1","price":9.99},{"sku":"B2","price":19.5}]"""// Without reified types, the element type would be erased.val products: List<Product> = mapper.readValue(jsonArray)println(products.size) // 2println(products.first().sku) // A1}
jacksonObjectMapper() is the concise factory (Kotlin module already registered). The reified readValue<T>() captures full generic types, so List element types survive deserialization.
import com.fasterxml.jackson.databind.DeserializationFeatureimport com.fasterxml.jackson.databind.SerializationFeatureimport com.fasterxml.jackson.databind.json.JsonMapperimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModuleimport com.fasterxml.jackson.module.kotlin.kotlinModuleval mapper = JsonMapper.builder().addModule(kotlinModule()) // Kotlin data-class support.addModule(JavaTimeModule()) // Instant/LocalDate as ISO-8601, not epoch numbers.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
A production-style mapper: Kotlin support, java.time as ISO-8601 strings, and tolerance for unknown fields from evolving external APIs.
import com.fasterxml.jackson.databind.DeserializationFeatureimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizerimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.web.bind.annotation.*data class CreateUserRequest(val name: String, val email: String)@RestController@RequestMapping("/users")class UserController {@PostMappingfun create(@RequestBody body: CreateUserRequest): String ="Created " + body.name // Spring deserializes JSON into the data class for you}@Configurationclass JacksonConfig {@Beanfun kotlinCustomizer() = Jackson2ObjectMapperBuilderCustomizer { b ->b.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)}}
In Spring Boot 3 you rarely build the mapper for controllers; with jackson-module-kotlin on the classpath, @RequestBody binds directly into a data class. Customize the auto-configured mapper via a customizer bean.
🧠 Check your understanding
0/1 · 0/1 answered1. Why does a plain ObjectMapper without jackson-module-kotlin often fail to deserialize JSON into an immutable Kotlin data class?