Kotlin Serialization: The Ultimate Introduction for Developers
In the world of modern software development, data interchange is a fundamental requirement. Whether you’re building a mobile app, a web service, or a desktop application, the need to convert data between different formats – such as objects in your program and a transferable representation like JSON or XML – is ubiquitous. This process is known as serialization (object to transferable format) and deserialization (transferable format back to object). For Kotlin developers, kotlinx.serialization emerges as a powerful, type-safe, and cross-platform solution provided by JetBrains.
This article will serve as your ultimate introduction to Kotlin Serialization, guiding you through its core concepts, setup, basic usage, advanced features, and why it’s becoming the go-to choice for many Kotlin projects.
What is Kotlin Serialization?
kotlinx.serialization is a multiplatform serialization library for Kotlin. Unlike many traditional serialization frameworks that rely on reflection, Kotlin Serialization uses a compiler plugin. This design choice offers significant benefits, primarily improved runtime performance and smaller binary sizes, as it avoids the overhead associated with reflection.
It allows you to:
* Serialize Kotlin objects into various formats (e.g., JSON, CBOR, Protocol Buffers).
* Deserialize data from these formats back into Kotlin objects.
Why Choose Kotlin Serialization?
Several compelling reasons make kotlinx.serialization an excellent choice for your projects:
- No Reflection: As mentioned, the compiler plugin approach eliminates reflection, leading to faster execution and smaller application footprints.
- Type Safety: It fully leverages Kotlin’s robust type system. This means that during deserialization, the library ensures that the data conforms to the expected type, significantly reducing
ClassCastExceptionorNullPointerExceptionerrors at runtime. - Cross-Platform Compatibility: Designed with Kotlin Multiplatform in mind,
kotlinx.serializationworks seamlessly across JVM, JavaScript, and Native targets, making it ideal for shared codebases. - Idiomatic Kotlin: It integrates natively with Kotlin, allowing you to define serializable data classes using familiar Kotlin constructs, leading to cleaner and more readable code.
- Extensibility: The library is highly extensible, providing mechanisms for custom serializers, allowing you to handle complex or custom data types with ease.
Getting Started: Setup
To integrate Kotlin Serialization into your project, you need to add the serialization Gradle plugin and the necessary runtime dependencies. For JSON serialization, the most common use case, add the following to your build.gradle.kts file:
“`kotlin
plugins {
// Apply the Kotlin JVM plugin (replace with ‘kotlin(“multiplatform”)’ for multiplatform projects)
kotlin(“jvm”) version “1.9.23”) // Use your project’s Kotlin version
// Apply the Kotlin Serialization plugin
kotlin(“plugin.serialization”) version “1.9.23”) // Must match your Kotlin version
}
dependencies {
// Add the JSON serialization runtime library
implementation(“org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2”) // Use the latest stable version
}
“`
Remember to replace the version numbers with the latest stable releases for Kotlin and kotlinx.serialization.
Basic Usage: Serialize and Deserialize
Once set up, making a Kotlin class serializable is as simple as annotating it with @Serializable. You then use the kotlinx.serialization.json.Json object to perform encoding (serialization) and decoding (deserialization).
Let’s look at an example:
“`kotlin
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
// Mark the data class as Serializable
@Serializable
data class User(val id: String, val name: String, val email: String?)
fun main() {
val user = User(“123”, “Alice”, “[email protected]”)
// 1. Serialization: Kotlin object to JSON string
val jsonString = Json.encodeToString(user)
println("Serialized JSON: $jsonString")
// Output: Serialized JSON: {"id":"123","name":"Alice","email":"[email protected]"}
// 2. Deserialization: JSON string back to Kotlin object
val decodedUser = Json.decodeFromString<User>(jsonString)
println("Deserialized User: $decodedUser")
// Output: Deserialized User: User(id=123, name=Alice, email=null)
// Handling nullable properties during deserialization
val jsonWithoutEmail = """{"id":"456","name":"Bob"}"""
val userWithoutEmail = Json.decodeFromString<User>(jsonWithoutEmail)
println("User without email: $userWithoutEmail")
// Output: User without email: User(id=456, name=Bob, email=null)
}
“`
In this example:
* The @Serializable annotation tells the Kotlin Serialization compiler plugin to generate the necessary serialization logic for the User data class.
* Json.encodeToString(user) converts the user object into its JSON string representation.
* Json.decodeFromString<User>(jsonString) parses the JSON string and reconstructs a User object. The <User> is a reified type parameter, allowing the compiler to know the target type at compile time.
Supported Formats
While JSON is the most commonly used, Kotlin Serialization supports various data formats through separate modules:
- JSON:
kotlinx-serialization-json - CBOR:
kotlinx-serialization-cbor(Concise Binary Object Representation) - Protocol Buffers:
kotlinx-serialization-protobuf - Properties:
kotlinx-serialization-properties(for.propertiesfiles) - HOCON:
kotlinx-serialization-hocon(JVM only, for Human-Optimized Config Object Notation)
You only need to include the dependencies for the formats you intend to use.
Advanced Concepts and Features
Kotlin Serialization offers a rich set of features for more complex scenarios:
-
Custom Serializers: For types that are not natively supported (e.g.,
java.time.LocalDateTimein JVM projects) or when you need highly specific serialization logic, you can implement theKSerializerinterface. This allows you to define how a particular type should be encoded and decoded.“`kotlin
// Example (simplified) for a custom LocalDateTime serializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.LocalDateTime
import java.time.format.DateTimeFormatterobject LocalDateTimeSerializer : KSerializer
{
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(“LocalDateTime”, PrimitiveKind.STRING)override fun serialize(encoder: Encoder, value: LocalDateTime) { encoder.encodeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) } override fun deserialize(decoder: Decoder): LocalDateTime { return LocalDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME) }}
@Serializable
data class Event(@Serializable(with = LocalDateTimeSerializer::class) val timestamp: LocalDateTime, val name: String)
“` -
Polymorphic Serialization: This feature is crucial when dealing with class hierarchies (e.g., sealed classes or interfaces) where the actual type of an object might vary.
kotlinx.serializationprovides robust mechanisms to handle these cases, allowing you to serialize and deserialize objects of different subtypes within a common base type. You typically configure aJsoninstance withserializersModuleto register your polymorphic types. -
@SerialName: If the name of a property in your Kotlin data class differs from the key name in your target serialization format (e.g., a JSON field name), you can use the@SerialNameannotation to map them.kotlin
@Serializable
data class Product(
@SerialName("product_id") val id: String,
val name: String,
@SerialName("item_price") val price: Double
) -
@Transient: To exclude a property from the serialization process, mark it with@Transient. This is useful for internal properties or computed values that shouldn’t be part of the serialized output. Properties without backing fields are automatically excluded.kotlin
@Serializable
data class Report(val title: String) {
@Transient val creationDate: String = "2026-01-14" // Will not be serialized
fun generateContent() = "Content for $title"
} -
Default Values and Optional Properties: Kotlin Serialization gracefully handles missing fields during deserialization if you provide default values in your data class. If a field is absent in the input, the default value will be used, making your deserialization more robust.
kotlin
@Serializable
data class Config(val timeoutSeconds: Int = 30, val enableLogging: Boolean = true)
// If JSON is {"timeoutSeconds": 60}, enableLogging will default to true
Conclusion
Kotlin Serialization stands out as a modern, efficient, and type-safe solution for data serialization in Kotlin. Its reflection-free approach delivers performance benefits, while its cross-platform nature and strong integration with Kotlin’s type system make it an indispensable tool for developers building robust and maintainable applications across various environments.
By understanding its core principles, setting it up correctly, and leveraging its advanced features, you can streamline your data interchange operations and write cleaner, more reliable Kotlin code. As the Kotlin ecosystem continues to grow, kotlinx.serialization is poised to remain a cornerstone technology for handling data.