Question
In an Android project using Kotlin, is there a convenient way to make a data class implement Parcelable without manually writing writeToParcel, describeContents, and CREATOR code?
For example, how can this class be made parcelable with minimal boilerplate?
data class User(
val id: Long,
val name: String
)
Short Answer
You will learn how Android parceling works, why Kotlin data classes do not become parcelable automatically, and how the @Parcelize compiler plugin generates the required Parcelable implementation for you.
Concept
Parcelable is an Android interface used to serialize an object into a Parcel: a compact container Android uses to transfer data between components such as activities, fragments, services, and saved state.
A Kotlin data class automatically generates useful methods such as equals(), hashCode(), toString(), copy(), and componentN(). However, those generated methods are unrelated to Android's Parcelable interface. A data class is not parcelable unless you explicitly make it so.
The modern Kotlin solution is @Parcelize. It is a Kotlin compiler plugin that generates the parceling code at compile time. You write the class properties and annotate the class; Kotlin generates the otherwise repetitive Parcelable implementation.
This matters because manual Parcelable code is easy to get wrong, especially when a class changes over time. With @Parcelize, the generated implementation stays aligned with the properties declared in the class.
Mental Model
Think of a data class as a neatly labeled form containing information, such as a user's ID and name.
A Parcel is like a small sealed delivery box that Android can carry from one screen to another. A normal data class tells Kotlin how to compare and print the form, but it does not tell Android how to pack the form into the box.
@Parcelize is an automatic packing machine. You describe the fields once, and the compiler creates the packing and unpacking instructions for Android.
Syntax and Examples
First, enable the Parcelize plugin in your Android app module's Gradle configuration.
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.parcelize")
}
Then import Parcelize, implement Parcelable, and add the annotation:
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class User(
val id: Long,
val name: String
) : Parcelable
@Parcelize instructs the Kotlin compiler to generate the required parceling code. The : Parcelable part is still important: it declares that callers may use a User wherever Android expects a parcelable value.
You can use the object in an Intent:
val user = User(id = 42L, name = "Mina")
val intent = Intent(this, ProfileActivity::class.java)
intent.putExtra("user", user)
startActivity(intent)
Retrieve it on a recent Android API level with the type-safe overload:
val user = intent.getParcelableExtra("user", User::class.java)
For compatibility code that supports Android versions before API 33, use AndroidX IntentCompat:
import androidx.core.content.IntentCompat
val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)
Step by Step Execution
Consider this class:
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Product(
val sku: String,
val quantity: Int,
val inStock: Boolean
) : Parcelable
When the app is compiled:
data classcauses Kotlin to generateequals,hashCode,toString,copy, and destructuring functions.: Parcelabledeclares thatProductmust satisfy Android's parcelable contract.@Parcelizeactivates generated parceling support for this class.- The compiler generates code that writes
sku,quantity, andinStockinto aParcelin a consistent order. - It also generates code that reads those values back in the same order and constructs a new
Product.
At runtime:
val original = Product("keyboard-01", 2, true)
intent.putExtra("product", original)
Android parcels the object when it needs to pass it across the component boundary. In the destination:
val received = IntentCompat.getParcelableExtra(
intent,
"product",
Product::class.java
)
received is a new Product instance containing the same property values as original.
Real World Use Cases
Common uses for parcelable data classes include:
- Navigation arguments: Pass a small selected item, such as a
ProductorUser, to a detail screen. - Activity and fragment arguments: Provide screen configuration or an initial selection.
- Service communication: Send a small request or result object through Android component APIs that require
Parcelable. - Saved UI state: Store lightweight screen state in a
Bundleso it can be restored after recreation. - Custom views: Save and restore a view's state when Android recreates the UI.
For example, a checkout screen might receive a parcelable order draft:
@Parcelize
data class OrderDraft(
val customerId: String,
val itemIds: List<String>,
val couponCode: String?
) : Parcelable
Real Codebase Usage
In production code, @Parcelize is typically used for small objects that cross an Android framework boundary. Keep those objects focused on the values the receiving component actually needs.
A common navigation pattern is to pass an identifier rather than a complete database model:
@Parcelize
data class ProductArgs(
val productId: Long
) : Parcelable
The destination reads productId and loads current data from a repository. This avoids several problems:
- Large parcelables are slower to copy.
- The passed object may be stale when the user reaches the next screen.
- UI navigation becomes less tightly coupled to a database or network model.
Use validation and guard clauses after receiving an argument when appropriate:
val args = IntentCompat.getParcelableExtra(intent, "product_args", ProductArgs::class.java)
?: return finish()
viewModel.loadProduct(args.productId)
For state that must survive process death, make sure every value stored in a Bundle is supported by Android's state-saving system. Parcelable can help, but it is still best to save only the minimum state needed to restore the screen.
Common Mistakes
Forgetting : Parcelable
The annotation does not replace the interface declaration.
// Incorrect
@Parcelize
data class User(val id: Long)
// Correct
@Parcelize
data class User(val id: Long) : Parcelable
Forgetting the Gradle plugin
If @Parcelize is unresolved or the compiler does not generate the implementation, add the Parcelize plugin to the module that contains the class:
plugins {
kotlin("plugin.parcelize")
}
Using the old kotlin-android-extensions approach
Older tutorials may use kotlin-android-extensions or an experimental parcelize package. Do not add that deprecated plugin for new projects. Use kotlin("plugin.parcelize") and kotlinx.parcelize.Parcelize.
Passing large object graphs
Avoid putting large lists, images, database entities with many relationships, or large JSON-like payloads into an Intent or Bundle. Android transactions have size limits, and oversized data can cause runtime failures. Pass an ID, URI, or small set of arguments instead.
Assuming every custom type works automatically
Primitive values, strings, many collections, and parcelable nested objects are commonly supported. A custom property type may need to implement Parcelable, be serializable through another supported mechanism, or use a custom parceler.
@Parcelize
data class ScreenArgs(
val user: User
) : Parcelable
@Parcelize
data class User(
val id: Long,
val name: String
) : Parcelable
Reading an extra without checking for null
An extra can be absent, malformed, or unavailable. Treat it as nullable unless your navigation layer guarantees it.
val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)
?: return finish()
Comparisons
| Approach | Best for | Advantages | Drawbacks |
|---|---|---|---|
@Parcelize | Android component arguments and small saved state | Minimal boilerplate; Android-native | Android-specific; should not carry large data |
Manual Parcelable | Rare special serialization requirements | Full control over how values are written | Verbose and error-prone |
Serializable | Usually legacy Java code only | Simple interface declaration | Generally less efficient for Android IPC-style transfers |
| Passing an ID | Most navigation and detail screens | Small, current data can be loaded at destination | Requires a repository or data source lookup |
Bundle primitives | A few simple values | No custom class required | Becomes difficult to maintain as arguments grow |
@Parcelize and data class solve different jobs. A data class reduces value-object boilerplate; @Parcelize generates Android parceling boilerplate. They work well together but neither replaces the other.
Cheat Sheet
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class User(
val id: Long,
val name: String,
val isAdmin: Boolean = false
) : Parcelable
Module plugin:
plugins {
kotlin("plugin.parcelize")
}
Pass through an intent:
intent.putExtra("user", user)
Read compatibly with AndroidX:
val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)
Rules:
- Add both
@Parcelizeand: Parcelable. - Import
kotlinx.parcelize.Parcelize. - Keep parcelables small.
- Prefer IDs for large, mutable, or repository-backed data.
- Nested custom objects should also be parcelable or otherwise supported.
- Treat received extras as nullable unless guaranteed by your app's contract.
FAQ
Does Kotlin make every data class Parcelable automatically?
No. Data classes generate value-related functions, not Android parceling code. Add @Parcelize and : Parcelable.
What dependency is needed for @Parcelize?
In a standard Android Kotlin module, enable the Kotlin Parcelize Gradle plugin with kotlin("plugin.parcelize"). Then import kotlinx.parcelize.Parcelize.
Is @Parcelize better than writing Parcelable manually?
For ordinary value classes, yes. It removes repetitive code and reduces the chance that writing and reading order get out of sync. Write manual parceling only when you need special control.
Can a parcelable data class contain another data class?
Yes, if the nested custom type is also parcelable or is otherwise supported by the generated parceling mechanism.
Should I pass a complete API or database model between activities?
Usually no. Prefer a small argument object or an ID, then load the current data in the destination screen. This keeps parcels small and reduces stale-data issues.
Is Serializable a replacement for Parcelable on Android?
It can work in some cases, but Parcelable is the Android-focused mechanism for this purpose. For Kotlin Android code, @Parcelize is usually the convenient choice.
Why is my parcelable extra null?
The sender may not have added the extra, the key may differ, or the object may not match the expected type. Use one shared constant or a typed navigation mechanism and handle missing values safely.
Mini Project
Description
Build a small profile-launch flow. The first activity creates a ProfileArgs data class and sends it to a profile activity. The profile activity safely reads the parcelable argument and displays its values. This demonstrates a realistic use of @Parcelize for small navigation arguments.
Goal
Pass a typed profile argument between two Android activities without writing manual Parcelable methods.
Requirements
Create a ProfileArgs data class with an ID, display name, and premium-status flag.
Enable the Parcelize plugin and make ProfileArgs implement Parcelable using @Parcelize.
Send a ProfileArgs instance from MainActivity to ProfileActivity in an intent extra.
Read the extra safely in ProfileActivity using IntentCompat.
Close ProfileActivity if the required argument is missing.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.