Blog Infos
Author
Published
Topics
, , , ,
Published

 

Kotlin object is the standard way to declare a singleton — a single, globally accessible instance created once per JVM.

This guarantee holds at the language level. But in real-world projects, it can break — without compiler errors or visible warnings.

One common cause is serialization. Some libraries return a new instance during deserialization, breaking reference equality and shared state.

This article explains when Kotlin singletons stop being singletons — and how to avoid it in practice.

Featured On
How Gson breaks Kotlin object identity

Most developers use object in Kotlin as a language-level singleton. It’s created once, holds global state, and is referenced consistently throughout the app.

But this guarantee only applies within the same classloader — and only if the object is used directly. Runtime tools like serializers can break this behavior without warning.

Here’s what happens when you serialize and deserialize a Kotlin object using Gson:

object MySingleton {
    const val NAME: String = "MySingleton"
}

fun main() {
    val gson = Gson()

    // serialize
    val json = gson.toJson(MySingleton)

    // deserialize
    val deserialized = gson.fromJson(json, MySingleton::class.java)

    println("MySingleton before serialization hashCode: ${System.identityHashCode(MySingleton)}")
    println("MySingleton after serialization hashCode: ${System.identityHashCode(deserialized)}")

    println("Same instance: ${deserialized === MySingleton}")
}

Output:

MySingleton before serialization hashCode: 399534175  
MySingleton after serialization hashCode: 428910174  
Same instance: false

Even though the original type is object, Gson creates a new instance on deserialization. Reference equality is lost, and any global state held inside the singleton is not preserved.

Why this happens

Gson does not recognize Kotlin’s object. It treats it as a regular class with fields. During deserialization, it uses reflection to create a new instance — even though object was designed to be a singleton.

This is not a bug in Gson. It’s a mismatch between a Kotlin-specific construct and a library that works at the Java class level.

What to remember

Kotlin object guarantees a single instance per classloader — at the language level.

Serialization libraries like Gson treat it as a regular class and create new instances during deserialization.

If identity matters, using object with serialization requires explicit handling.

To preserve singleton behavior, use a custom adapter that always returns the original instance.

How kotlinx.serialization preserves object identity

Kotlin’s official serialization library does recognize object. When annotated with @Serializable, the deserializer knows it’s a singleton — and returns the existing instance.

@Serializable
object MySingleton {
    const val NAME: String = "MySingleton"
}

@OptIn(InternalSerializationApi::class)
fun main() {
    val json = Json { encodeDefaults = true }

    // serialize
    val serialized = json.encodeToString(MySingleton)

    // deserialize
    val deserialized = json.decodeFromString(MySingleton::class.serializer(), serialized)

    println("MySingleton before serialization hashCode: ${System.identityHashCode(MySingleton)}")
    println("MySingleton after serialization hashCode: ${System.identityHashCode(deserialized)}")

    println("Same instance: ${deserialized === MySingleton}")
}

Output:

MySingleton before serialization hashCode: 399534175  
MySingleton after serialization hashCode: 399534175  
Same instance: true

This behavior is by design. The Kotlin serialization plugin understands object declarations and reuses the existing instance safely.

How Moshi handles Kotlin object

Moshi takes a stricter approach. Instead of creating a new instance, it throws an exception when asked to serialize or deserialize a Kotlin object.

object MySingleton {
    const val NAME: String = "MySingleton"
}

fun main() {
    val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

    val adapter = moshi.adapter(MySingleton::class.java)

    val json = adapter.toJson(MySingleton)
    val deserialized = adapter.fromJson(json)
}

Output:

Exception in thread "main" java.lang.IllegalArgumentException: 
Cannot serialize object declaration com.example.MySingleton

This behavior prevents accidental duplication. To deserialize a Kotlin object with Moshi, you must write a custom adapter that always returns the original instance.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Summary

Kotlin object works reliably as a singleton — but only at the language level.

With serialization, that guarantee can break.

  • Gson creates a new instance on deserialization
  • Moshi throws an error and refuses to serialize
  • kotlinx.serialization preserves the original instance

If your app relies on identity, reference equality, or shared state — use caution when combining object with third-party serializers.

In Kotlin-first projects, prefer kotlinx.serialization when working with object.

If you found this article helpful

If you found this article helpful, consider leaving a clap — it helps others discover it.
You can also follow me on Medium for more articles about Kotlin, Android development, and practical engineering topics.

You might also like:

Anatolii Frolov
Senior Android Developer
Writing honest, real-world Kotlin & Jetpack Compose insights.
📬 Follow me on Medium

This article was previously published on proandroiddev.com.

Menu