Blog Infos
Author
Published
Topics
, , , ,
Published

Hey there, fellow Android devs! Serialization is as fundamental to our jobs as coffee and chatGPT. We’re constantly converting data, network responses, file reads, or passing data between activities. It’s easy to get lost in the sea of tools: Parcelable, @Parcelize, Gson, Moshi, kotlinx.serialization, and java.io.Serializable. This guide cuts through the confusion, let you know what are the available tools, and why they work the way the do. This way, you’ll be able make the right choice.

In the end, it’ll provide visual charts to help you memorize the gist since our brains remember colors and shapes better than plain text (thank you, evolution).

Ice Age geometric signs. From ideas.ted.com

Stone Age implementation of Parcelable

Section 1: What the Heck is De/Serialization Anyway?

Serialization is the process of converting an object into a format that can be easily stored or transmitted. Think of it like packing your lunch. You can’t just throw a sandwich, an apple, and a drink into your bag. You put them in containers, maybe wrap the sandwich, and then put everything neatly in your lunchbox. That’s serialization. Deserialization is the reverse process: taking that packed lunch and getting your sandwich, apple, and drink back out, ready to eat.

In mobile development, we do this all the time. When we get a JSON response from a server, we need to deserialize it into objects our app can work with. When we want to pass a complex data object from one screen to another, we serialize it, put it in a Bundle, and then deserialize it on the other side. It’s the magic that lets data move around in our apps.

Section 2: The Rogues’ Gallery of Serialization Tools

Let’s meet the usual suspects and figure out when to call on each one.

java.io.Serializable

This is the OG of Java serialization. It’s a marker interface, meaning you just slap implements Serializable on your class and you’re done. Easy, right? Well, not so fast.

  • When to use it: Honestly? Almost never in modern Android development. It’s simple, but it’s slow because it uses java reflection, which is a fancy way of saying it does a lot of work at runtime to figure out how to serialize and deserialize your objects. It also creates a lot of temporary objects, which can trigger the garbage collector and make your app janky.
  • Common mistakes: The biggest mistake is using it at all for performance-critical code. Another common issue is the serialVersionUID. If you don’t define one, the Java runtime generates one for you based on the class structure. If you change the class (add a field, for example), the generated ID changes, and you’ll get an InvalidClassException when you try to deserialize old data. Forgetting to make all fields in the class Serializable is another classic blunder(nested Serializables).
  • Best practices: The best practice is to avoid it. But if you absolutely must use it (maybe for some old legacy code), always define a serialVersionUID.
import java.io.Serializable;

public class Hi implements Serializable {
private static final long serialVersionUID = 1L;
private String positiveHand;
private int dots;
// getters and setters
}
Parcelable

This is Android’s answer to Serializable. It’s an interface designed specifically for high-performance IPC (Inter-Process Communication) on Android. It’s much faster than Serializable because it’s explicit about the serialization process. You have to write the code to parcel and unparcel your object yourself.

  • When to use it: When you need to pass data between Android components, like activities or fragments, via a Bundle. It’s the recommended way to do this for performance.
  • Common mistakes: The boilerplate. Oh, the boilerplate. You have to implement writeToParcel() and a CREATOR companion object. It’s easy to make a mistake, like writing the fields in a different order than you read them, which will lead to some very confusing bugs. Another common mistake is forgetting to make nested objects Parcelable as well.
  • Best practices: Before @Parcelize, the best practice was to use a plugin to generate the boilerplate for you. Now, the best practice is to just use @Parcelize.

 

data class Hi(
val positiveHand: String?,
val dots: Int
) : Parcelable {

constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readInt()
)

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(positiveHand)
parcel.writeInt(dots)
}

override fun describeContents(): Int = 0

companion object CREATOR : Parcelable.Creator<Hi> {
override fun createFromParcel(parcel: Parcel): Message = Hi(parcel)
override fun newArray(size: Int): Array<Hi?> = arrayOfNulls(size)
}
}
@Parcelize

This is a Kotlin annotation that makes using Parcelable a breeze. You just add the @Parcelize annotation to your data class, and the Kotlin compiler generates all that asterisk and dots boilerplate for you. It’s carved on the wall for you.

  • When to use it: Whenever you would use Parcelable in a Kotlin project. There’s really no reason not to.
  • Common mistakes: The most common issue is forgetting to add the kotlin-parcelize plugin to your build.gradle file. Another is trying to use it on a class that isn’t a data class or has properties in the class body that aren’t in the primary constructor. Also, be aware that it was an experimental feature for a while, so if you’re on an older version of Kotlin, you might run into some issues.
  • Best practices: Just use it! It’s the modern, idiomatic way to do Parcelable in Kotlin.
@Parcelize
data class Hi(
val positiveHand: String,
val dots: Int
) : Parcelable
Gson

Gson is a library from Google for converting Java objects to and from JSON. For a long time, it was the go-to library for networking and easy serialization in Android apps.

  • When to use it: If you have a Java-heavy project or are stuck with it in a legacy codebase. It’s still a solid library, but it has its issues.
  • Common mistakes: Gson uses reflection, which, as we’ve discussed, can be slow and doesn’t play well with ProGuard/R8 if you’re not careful. A common mistake is not configuring ProGuard correctly, which leads to crashes in release builds because it renames your classes and fields. Another issue is that it can be a bit too lenient and might deserialize a JSON with missing fields into an object with null properties, which can lead to NullPointerExceptions down the line if you’re not using Kotlin’s null safety features.
  • Best practices: If you have to use Gson, make sure your ProGuard rules are set up correctly. Also, consider using a TypeAdapter for complex or custom serialization logic. And if you’re starting a new project, especially a Kotlin-first one, you should probably look at Moshi or kotlinx.serialization instead.
data class Hi(
val positiveHand: String?,
val dots: Int
)

fun main() {
val gson = Gson()
val message = Hi(positiveHand = "*", dots = 5)
val json = gson.toJson(message)

val jsonString = """{"positiveHand":"*","dots":5}"""
val parsedMessage = gson.fromJson(jsonString, Hi::class.java)
}
Moshi

Moshi is a modern JSON library for Android, Java, and Kotlin from the fine folks at Square. It was designed to use reflection but be a better Gson.

  • When to use it: For JSON serialization in modern Android apps, especially those written in Kotlin. It has first-class support for Kotlin and can use code generation to avoid reflection, which makes it faster and safer with ProGuard.
  • Common mistakes: Not using the code-gen module (moshi-kotlin-codegen). If you just use the reflection-based KotlinJsonAdapterFactory, you’re missing out on the performance and safety benefits. Another common issue is dealing with polymorphic JSON (when you have a list of items of different types). You need to set up a PolymorphicJsonAdapterFactory for that, which can be a bit tricky at first.
  • Best practices: Use the moshi-kotlin-codegen module. It will save you a lot of headaches. Also, take advantage of JsonAdapters for custom serialization logic. They are very powerful.
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Hi(
val positiveHand: String?,
val dots: Int
)
fun main() {
val adapter = Moshi.Builder().build().adapter(Hi::class.java)

val message = Hi(positiveHand = "*", dots = 5)
val json = adapter.toJson(message)

val jsonString = """{"positiveHand":"*","dots":5}"""
val parsedMessage = adapter.fromJson(jsonString)
}
kotlinx.serialization

This is JetBrains’ own serialization library. It’s multiplatform, so you can use it on Android, iOS (with Kotlin Multiplatform), and even in your backend code.

  • When to use it: If you’re building a Kotlin Multiplatform project, this is the obvious choice. It’s also a great option for any Kotlin-first Android app. It’s completely reflection-free and uses a compiler plugin to generate the serialization code.
  • Common mistakes: The setup can be a bit more involved than with other libraries. You need to apply the plugin and add the dependencies. I usually just use latest kotlinLang setup. Another common issue is that it can be a bit stricter than other libraries. For example, by default, it will throw an exception if it encounters a field in the JSON that isn’t in your data class. You can configure it to be more lenient, but it’s something to be aware of.
  • Best practices: Embrace the strictness! It can help you catch bugs early. Also, explore the different formats it supports. It’s not just for JSON; it can also handle Protobuf, CBOR, and more.

Provides JSONProtobufCBORHocon and Properties formats.

Complete multiplatform support: JVM, JS and Native.

Colors and Shapes

As promised, here is the comparision to give you a quick understanding to help you recall the key differences. I added few more tools to make it future proof.

´Scatter plot showing various data transfer and serialization tools for Android, with scope on the x-axis (Local → Network → Persistent) and performance cost on the y-axis (lower is more efficient). Each tool is represented by a colored point, with green indicating high efficiency and red indicating low efficiency.

Comparison of Android data transfer and serialization tools.

Here, we have a comparison of Android data transfer and serialization tools. Tools like FlatBuffers and @Parcelize/Parcelable are most efficient (green), while Java Serializable and File I/O have higher performance costs (red). The x-axis represents the scope from local storage to network and persistent storage. We use a color guide here with Green 🟢 indicating a fast tool and transitioning to Red 🔴 as slower solutions.

Still not sure when to use which?

Alt Text A dark-themed horizontal bar chart titled ‘When to Use Each Android Data Handling Tool (Speed Rating)’. The chart maps six development situations to recommended serialization and persistence tools, color-coded by speed. Green (Fast/Optimized) is used for: Passing data between Android components (@Parcelize/Parcelable), Modern/Optimized JSON Serialization (kotlinx.serialization or Moshi), and Fastest Cross-Process IPC (AIDL, ProtoBuf, or FlatBuffers). Yellow (Slower/Situational/Avoid) is

Visual illustrating on which data handling tool to choose for a specific task.

Above is an essential visual guide for Android developers, illustrating which data handling tool to choose for a specific task. The chart is color-coded to reflect performance: Green 🟢 highlights fast, optimized methods (like Parcelable and non-reflection-based serializers), while Yellow 🟡 indicates methods that are slower or situational (like Gson or disk I/O layers).

Section 3: Important Notes

So, here are a few hard-won truths, because nobody wants to learn these by having their app blow up in production (trust me, I’ve got the scars and the Stack Overflow badges to prove it):

  • First, a cardinal rule: Don’t pass Drawables or Bitmaps directly. Seriously, just don’t. These heavy objects aren’t meant for serialization. It might seem fine on your dev device, but it’s a ticking time bomb for TransactionTooLargeException or out-of-memory errors. Save the image to storage and pass its URI instead. Your users (and their phone batteries) will thank you.
  • Next, remember that migration is a marathon, not a sprint. If you’re updating a legacy codebase, don’t just rip out Gson and slap in Moshi. Prioritize app stability. Check for breaking points like ProGuard rules and lenient deserialization configs. Plan a gradual migration, not a “big bang” that turns your app into a bug-ridden mess.
  • Also, embrace null safety. JSON can be a wild west of missing fields. Be explicit about nullability in your Kotlin data classes (String? vs. String). Handle nulls gracefully, or you’ll be staring down a NullPointerException faster than you can say, “But it worked on my machine!”

Don’t over-optimize prematurely.

  • Yes, Parcelable is faster than Serializable, but for small, infrequent data transfers, the difference is negligible. Focus on readable, maintainable code first. Only optimize when profiling shows a clear bottleneck.
  • Keep your ProGuard/R8 rules tight. This is where many developers has shed a tear. Reflection-based libraries can be a nightmare with code shrinking. Always include the necessary keep rules for your serialization libraries and data models. A quick search for “[library name] proguard rules” will save you a world of pain.
  • Versioning is crucial for long-term sanity. Your data models will change. Plan for schema evolution with tools like @SerialName or default values. Serializable is notoriously bad at this, so think ahead about how your app will handle data from older versions.
  • Finally, don’t just deserialize anything. Deserializing untrusted data, especially with java.io.Serializable, is a major security risk. It’s been called a “horrible mistake” for a reason. Always validate incoming data to prevent injection attacks and other nasty surprises. Trust, but verify.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Section 4: Conclusion

So there you have it. A tour of the world of Android serialization. It might seem like a lot to take in, but it really boils down to a few key principles.

For passing data between components, use @Parcelize. For JSON, use Moshi with code-gen or kotlinx.serialization. And for the love of all that is holy, try to avoid java.io.Serializable.

Now go forth and serialize with confidence! And if you see a junior dev struggling with this stuff, be a good senior dev and send them a link to this article. You might just save them from a world of pain. Or at least from a few NullPointerExceptions 🙂

 

This article was previously published on proandroiddev.com

Menu