Kotlin Data Serialization

A comprehensive guide to implementing type-safe data serialization in your Android applications using kotlinx.serialization

What Is Data Serialization?

Serialization is the process of converting complex data structures into formats that can be easily transferred over a network or stored in persistent storage. The reverse process--deserialization--reconstructs runtime objects from those external formats.

In Android mobile development, serialization appears in critical scenarios:

  • Network Communication: Converting API request/response data
  • Local Storage: Caching user data and preferences
  • Component Communication: Sharing data between app components
  • Third-Party Integrations: Formatting data for SDKs and services

The kotlinx.serialization library, developed by JetBrains, provides a robust, type-safe solution for Android app development. Unlike reflection-based approaches, it uses compiler plugins to generate efficient serialization code at compile time, resulting in better performance and smaller APK sizes due to reduced reflection overhead.

For applications that require seamless backend API integration, proper data serialization ensures reliable communication between mobile clients and server systems.

Key Serialization Formats

kotlinx.serialization supports multiple formats for different mobile use cases

JSON Serialization

The most common format for REST APIs. Human-readable and natively supported by most web services.

Protocol Buffers

Binary format offering compact payloads and fast parsing for high-frequency communication.

CBOR

Concise Binary Object Representation for bandwidth-efficient data transfer.

HOCON

Human-Optimized Config Object Notation for application configuration files.

Setting Up kotlinx.serialization

Adding the Plugin

Add the serialization plugin to your project's build configuration:

plugins {
 id 'org.jetbrains.kotlin.jvm' version '2.0.0'
 id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.0'
}

Adding the Runtime Dependency

dependencies {
 implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3'
}

The plugin version should match your Kotlin version for compatibility.

Verification Example

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

@Serializable
data class TestMessage(val text: String, val count: Int)

val message = TestMessage("Hello!", 42)
val json = Json.encodeToString(message)
// Output: {"text":"Hello!","count":42}

Integrating proper serialization from the start of your project prevents costly refactoring later when building scalable mobile applications.

Core Concepts: Making Classes Serializable

The @Serializable Annotation

The @Serializable annotation tells the compiler plugin to generate serialization code. Simply mark your data classes:

@Serializable
data class User(val id: Int, val name: String, val email: String)

Encoding: Objects to Strings

@Serializable
data class Product(val name: String, val price: Double, val inStock: Boolean)

val product = Product("Headphones", 79.99, true)
val json = Json.encodeToString(product)
// Output: {"name":"Headphones","price":79.99,"inStock":true}

Decoding: Strings to Objects

val json = """{"name":"Headphones","price":79.99,"inStock":true}"""
val product = Json.decodeFromString<Product>(json)

Customizing Json Configuration

val customJson = Json {
 prettyPrint = true
 encodeDefaults = false
 ignoreUnknownKeys = true
}

These serialization patterns align with modern web development best practices for building robust API integrations.

Working with Collections and Complex Structures

Serializing Lists and Arrays

Collections are fully supported with automatic JSON array handling:

@Serializable
data class Task(val id: Int, val title: String, val completed: Boolean)

val tasks = listOf(
 Task(1, "Learn Kotlin", true),
 Task(2, "Build App", false)
)

val json = Json.encodeToString(tasks)
// Output: [{"id":1,"title":"Learn Kotlin","completed":true},{"id":2,"title":"Build App","completed":false}]

val decoded = Json.decodeFromString<List<Task>>(json)

Nested Objects

Nested @Serializable classes work automatically:

@Serializable
data class Address(val street: String, val city: String)

@Serializable
data class Customer(val name: String, val address: Address)

val customer = Customer(
 name = "Jane",
 address = Address("123 Main St", "San Francisco")
)
// Serializes to nested JSON structure

Maps and Key-Value Data

@Serializable
data class Config(val settings: Map<String, String>)

val config = Config(mapOf(
 "theme" to "dark",
 "language" to "en-US"
))

For applications handling large datasets, consider how AI-powered data processing can optimize serialization workflows and improve app performance.

Customizing Serialization Behavior

Renaming Properties with @SerialName

Map Kotlin property names to different JSON keys:

@Serializable
data class UserProfile(
 @SerialName("user_id")
 val userId: Int,

 @SerialName("first_name")
 val firstName: String,

 @SerialName("email_address")
 val emailAddress: String
)

Output: {"user_id":500,"first_name":"Alex","email_address":"[email protected]"}

Optional Properties and Default Values

Nullable and default-valued properties work automatically:

@Serializable
data class Settings(
 val theme: String = "light",
 val notificationsEnabled: Boolean = true,
 val language: String? = null
)

// Properties with default values are omitted from output by default

Ignoring Unknown Keys

Handle API changes gracefully with ignoreUnknownKeys = true:

val flexibleJson = Json {
 ignoreUnknownKeys = true
}

// Won't throw errors if JSON contains extra fields

This flexibility is essential when building cross-platform solutions that integrate with evolving backend services.

Advanced Serialization: Custom Serializers

Creating Custom Serializers

Implement KSerializer for complete control:

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.UUID

object UuidSerializer : KSerializer<UUID> {
 override val descriptor = PrimitiveSerialDescriptor(
 serialName = "UUID",
 kind = PrimitiveKind.STRING
 )

 override fun serialize(encoder: Encoder, value: UUID) {
 encoder.encodeString(value.toString())
 }

 override fun deserialize(decoder: Decoder): UUID {
 return UUID.fromString(decoder.decodeString())
 }
}

@Serializable(with = UuidSerializer::class)
data class DeviceInfo(val deviceId: UUID, val name: String)

Use custom serializers for domain-specific types like UUIDs, dates, or monetary amounts. This level of control is essential when integrating with cross-platform mobile applications that require specific data formats for API communication.

Error Handling and Robust Deserialization

Common Deserialization Errors

  • MissingKeyException: Required field is missing from JSON
  • SerializationException: General serialization issues
  • JsonDecodingException: JSON structure problems

Safe Deserialization Patterns

@Serializable
data class User(val id: Int, val name: String, val email: String)

fun safeDeserialize(json: String): Result<User> {
 return try {
 val user = Json.decodeFromString<User>(json)
 Result.success(user)
 } catch (e: Exception) {
 Result.failure(e)
 }
}

// Usage
val result = safeDeserialize(json)
result.onSuccess { user -> /* Handle success */ }
 .onFailure { error -> /* Handle error */ }

Providing Fallback Values

@Serializable
data class UserSettings(
 val theme: String = "light",
 val notificationsEnabled: Boolean = true
)

fun loadSettings(json: String): UserSettings {
 return try {
 Json.decodeFromString<UserSettings>(json)
 } catch (e: Exception) {
 UserSettings() // Return defaults on error
 }
}

Robust error handling is critical for production Android applications that depend on external APIs. These patterns ensure your app remains stable even when receiving unexpected data.

Performance Considerations for Mobile

Optimization Tips

  • Minimize Deserialization Scope: Only parse data you need
  • Cache Deserialized Results: Avoid re-parsing the same data
  • Use Background Processing: Handle large datasets with coroutines

Stream-Based Processing for Large JSON

import kotlinx.serialization.json.decodeToSequence

@Serializable
data class Event(val id: Int, val name: String)

fun processLargeDataset(jsonArray: String) {
 jsonArray.byteInputStream().buffered().use { input ->
 Json.decodeToSequence<Event>(input).forEach { event ->
 // Process each item incrementally
 println("Processing: ${event.name}")
 }
 }
}

This approach processes data incrementally without loading everything into memory, which is essential for maintaining smooth performance in mobile app development. For complex data processing requirements, explore how AI automation services can enhance your data workflows.

Best Practices Summary

  1. Define a Central Json Object
object AppJson {
 val json = Json {
 ignoreUnknownKeys = true
 encodeDefaults = true
 prettyPrint = BuildConfig.DEBUG
 }
}
  1. Use Data Classes: Designed for holding data, work perfectly with serialization

  2. Design for Evolution: Configure Json to ignore unknown keys for API resilience

  3. Validate After Deserialization: Add domain-level validation for data integrity

  4. Use Sealed Classes for Polymorphic Data: Handle different response types safely

  5. Test Thoroughly: Write unit tests for serialization round-trips and error handling

Conclusion

Data serialization is foundational for cross-platform mobile development--enabling API communication, local storage, and component data sharing. The kotlinx.serialization library provides type-safe, efficient serialization through compile-time code generation.

Master the concepts in this guide to build reliable, performant Android applications that handle data effectively. Whether you're building native Android apps or cross-platform solutions, proper data serialization ensures your applications communicate seamlessly with services and maintain data integrity across devices.

For teams building both mobile and web applications, consistent serialization practices across web and mobile development create a unified data layer that simplifies maintenance and improves reliability.

Frequently Asked Questions

Need Help Building Your Mobile App?

Our team of Kotlin experts can help you implement robust data serialization and build production-ready Android applications.

Sources

  1. Kotlin Serialization Documentation - Official Kotlin documentation covering kotlinx.serialization library, supported formats, setup, and API usage

  2. Kotlin Serialization Guide (GitHub) - Comprehensive guide from the JetBrains team

  3. LogRocket: Understanding Kotlin Data Serialization - Practical developer guide with real-world examples

  4. Baeldung: kotlinx-serialization Project - Tutorial-style resource for project setup and implementation