Kotlin

What Are Kotlin Singleton Classes? Explained

What Are Kotlin Singleton Classes? Explained

Kotlin has no singleton keyword. It has object, and that one word gives you a class the compiler backs with exactly one instance for the whole life of the application.

You’ll find it all over Android and backend code. Usually it’s a database connection or a network client that the whole app shares, sometimes a config holder that everything reads from.

No locking is needed because of the Java Language Specification. Under Oracle’s Java SE 8 edition, section 12.4, class initialization is a strictly sequential step, not a concurrent one. Kotlin relies on that rule, which is why a Kotlin singleton class skips the synchronized blocks a Java implementation once needed.

What Is a Kotlin Singleton Class?

YouTube player

In Java you write it by hand: a private constructor, a static field and a static getInstance method. Kotlin replaces all of that with an object declaration, and the compiler makes sure only one instance ever exists.

The object keyword works the same whether the declaration sits at the top level of a file or is nested inside another class.

The pattern is old. The Gang of Four cataloged it in their Design Patterns book back in 1994, long before any JVM language was built around it. The problem it solves hasn’t changed much in software development since then. You have one shared resource or one piece of state, and it has to stay consistent across the whole codebase.

Declaring one takes very little. Name the object, put the logic in its body, and use that name wherever you need the singleton. The name goes right after object, the same way a class name follows class.

  • The instance is created the first time something references the object
  • You never call a constructor, anywhere

One thing that trips people up early on is that an object declaration isn’t an expression. You can’t put it on the right side of an assignment.

Kotlin mixes object-oriented and functional features on the JVM, and object is one of the places where that design shows up in everyday code.

How Does Kotlin Guarantee Thread Safety in a Singleton Object?

YouTube player

There’s no lock in your code. The JVM loads and initializes a class exactly once, even if several threads reach it at the same moment, and Kotlin builds on that.

Why is Kotlin becoming the new Java?

Discover Kotlin statistics: Android adoption, multiplatform growth, developer satisfaction, and the modern language evolution from JetBrains.

Explore Kotlin Data →

Kotlin’s documentation, current through the 2.4 release, says object declaration initialization is thread-safe and happens on first access. So you don’t need a synchronized keyword or double-checked locking.

Compare that with the usual Java version. It has a private static instance field and a null check, plus a synchronized block or a volatile flag, all wrapped in a getInstance method. I’ve reviewed plenty of these, and a surprising number got the volatile part wrong.

In Kotlin you declare the object once. The class loader takes care of the null check, the lock and the instance field.

That’s also why a Kotlin singleton class never needs a volatile flag or a lazy holder class the way older Java implementations did. The Java Memory Model’s class initialization rules already cover it.

How Does a Kotlin Singleton Appear in Java Bytecode?

YouTube player

Decompile one and you’ll find a plain Java class with a private constructor and a public static final INSTANCE field. Java callers go through that field like any other static member.

The object keyword has been part of the core language since Kotlin 1.0 shipped in February 2016 (JetBrains, 2016). The compiler emits one INSTANCE field per object declaration, and that’s the exact field Java call sites reference. There are a couple of other details worth knowing:

  • The constructor is private at the bytecode level, which blocks a second instantiation attempt from either language
  • Initialization runs inside a static initializer block, the same mechanism the JVM specification uses for any class-level setup

From Java, it’s just ClassName.INSTANCE. There’s no method call and no factory between the caller and the object.

You can check this yourself in IntelliJ IDEA’s Kotlin bytecode viewer. The Kotlin team chose this bytecode shape on purpose, because it maps onto a pattern the JVM already optimizes well.

Companion Object vs Enum Singleton vs Object Declaration: Which Kotlin Singleton Approach to Use

YouTube player

Most of the time a plain object declaration is what you want. A companion object makes sense when the single instance belongs to one specific class. If you need serialization protection, use the enum singleton, because the other two don’t give you that.

ApproachThread SafetyInstantiation TimingTypical Use Case
Object declarationGuaranteed by class loaderLazy, on first accessStandalone singleton, no host class
Companion objectGuaranteed by class loaderLazy, tied to host classFactory methods, class-level constants
Enum singletonGuaranteed by JVM enum rulesLazy, on first accessSingletons needing serialization safety
Java static classRequires manual synchronizationDepends on implementationLegacy codebases, pre-Kotlin projects

Companion Object Compared to a Singleton Object

The main difference is ownership. A companion object always belongs to a specific class, and you reach its members through that class’s name. A standalone object doesn’t need a host at all.

Looking to sharpen your Kotlin skills? Data classes, extension functions, null safety, and everything else you need - including let, apply, and scope functions - is on one page in the Kotlin Cheat Sheet.

Kotlin’s companion object concept explains how that binding works, including how the compiler names the generated companion class.

A class can have only one companion. From Java, companion members look like static members, minus the static keyword. A companion can also implement interfaces, so a class can satisfy a factory contract on its own.

My rule is simple. If the singleton behavior is a detail of one class, I use a companion. If it’s a concept in its own right, it gets its own object declaration.

Enum Singleton Compared to a Singleton Object

Joshua Bloch’s Effective Java, in its 2018 edition, names the single-element enum as the strongest guard against duplicate instances created through reflection or deserialization.

A plain object declaration doesn’t have that protection by default. If you serialize it and read it back, you can end up with a second instance unless you add a readResolve method by hand.

Kotlin supports the enum pattern directly through its enum class syntax, with a single constant standing in for the one allowed instance.

The downside is that an enum singleton can’t extend another class, because Kotlin enums already extend the built-in Enum class. It’s also more ceremony than I’d bother with for a value that never gets serialized.

Can a Kotlin Singleton Implement an Interface or Extend a Class?

Yes to interfaces, as many as you want. Kotlin’s documentation even shows an object implementing a mouse listener interface. An object can also extend an open class and inherit its members while staying a single instance.

What it can’t be is a superclass. An object can’t be marked open, so nothing can extend it.

That makes an object declaration a good fit for a one-time interface implementation, such as a click handler or a listener. A singleton that implements a strategy interface gives every caller the same behavior, with no factory or registry in between.

Where Are Kotlin Singleton Objects Used in Real Apps?

YouTube player

They’re used anywhere an app needs exactly one instance of something for its entire runtime. In practice that’s usually a database or an HTTP client, and sometimes app-wide configuration.

Android and Mobile Use Cases

On Android, many resources only make sense as one instance per process, so singletons come up constantly in Android development. Google’s own guidance uses the object-declaration pattern for shared resources.

Room is the classic example. Android’s documentation recommends exactly one Room database instance per app, reached through a companion object’s getInstance function. A second instance pointed at the same file risks corrupted writes and wastes memory.

Two other common cases are a reused Retrofit or OkHttp client (building a new one per request throws away connection pooling) and an application-scoped holder for state that has to survive across every screen.

Kotlin now dominates the platform. Android Developers documentation puts Kotlin code inside over 95% of the top 1,000 Android apps on Google Play. Kotlin’s own Android guide reports that over 50% of professional Android developers use it as their primary language, against 30% still writing Java.

Backend and Multiplatform Use Cases

On the server, singletons are mostly configuration managers and registries. A config manager reads environment variables once and exposes them to the rest of the code. A registry collects handlers or providers under one shared access point.

There are also utility objects, which just group stateless helper functions. They replace the static-method classes common in Java.

Kotlin Multiplatform code uses the same pattern across targets. A singleton written once in shared code runs on Android, iOS and desktop builds without a separate implementation for each.

Be careful with mutable state there, though. Not every target enforces threading rules the way the JVM does.

How Do You Create a Singleton in Kotlin?

YouTube player

You write one object declaration, and that’s most of the work. The compiler takes care of instance creation, thread safety and access. You don’t need a factory class or manual constructor logic.

The usual rules around constructors don’t apply, because an object declaration can’t take constructor parameters. Anything the singleton needs at startup goes in an init block.

  1. Declare the object with a descriptive name, right after the object keyword
  2. Add properties and functions directly inside the body, the same as inside a class
  3. Add an init block only if the singleton needs setup logic the first time it loads
  4. Reference members through the object’s name directly, with no instantiation step anywhere
  5. Confirm the single-instance behavior by comparing identity hash codes across two separate calls, or by checking the decompiled bytecode

Don’t skip that last step. In some plugin or multi-module setups, a singleton can get duplicated across class loaders without any warning, which defeats the whole point of using the pattern.

How Do You Test Code That Depends on a Kotlin Singleton Object?

You’ll hit the same problem Java developers have dealt with for years. Object declarations compile to final classes with a private constructor, which blocks the usual test-double tricks.

This changes how unit testing works around a singleton, because you can never create a fresh instance for an individual test case.

MockK and Mockito handle this differently. The difference starts to matter once your test suite grows past a handful of files.

Mocking a Kotlin Object with MockK

MockK has a dedicated mockkObject function. Baeldung’s testing documentation notes that most other mocking libraries struggle to mock a Kotlin singleton instance directly, which is a big reason Kotlin teams pick MockK.

Object mocks behave more like spies than regular mocks. According to MockK’s documentation, any method you leave unstubbed still calls the real implementation instead of throwing.

It works on standalone object declarations and companion objects, and on individual enum elements too. unmockkObject restores the real singleton afterward. There’s also a scoped block form that reverts the mock automatically when the block finishes.

With this, mocking in unit tests that touch a singleton becomes a normal step instead of a workaround.

Why Mockito Struggles with Kotlin Objects

Mockito was built for Java, and for years mocking a final class required a separate opt-in feature.

Since Mockito 5.0, the inline mock maker is the default, so Mockito can mock final classes, enums and final methods without extra setup. Mockito’s own documentation lists one exception: the inline mock maker can’t run on Android because of limitations in the Android VM.

So teams either apply the Kotlin all-open compiler plugin to the classes under test or switch to MockK whenever a singleton object is involved.

Speed matters here too. In a benchmark published by the 21 Buttons engineering team, the inline mock maker ran 2.7 times slower than the all-open plugin approach across 2,668 unit tests.

The cleaner fix is to put the singleton behind an interface and inject that interface into the class under test. Then the real singleton and a fake are interchangeable, and both problems mostly go away.

When Should You Avoid Using a Kotlin Singleton Object?

YouTube player

Singletons cause the most trouble when they hold global mutable state or run into Android lifecycles. The one-instance guarantee that makes them convenient also makes them hard to isolate or replace later, and code that calls them directly gets tightly coupled to them.

They’re still a fine choice for stateless utility functions and for read-only configuration loaded once at startup. More generally, they work for any shared state that doesn’t vary per user or per request.

I’d avoid them if:

  • The class needs to swap between a real and a fake implementation during tests
  • Multiple unrelated features start mutating the same shared object
  • An Android component holds a reference back to something the singleton owns
  • The object needs to survive serialization with its identity intact

When Dependency Injection Is the Better Choice

Android’s own documentation calls Hilt the officially recommended library for dependency injection in Android. It’s built on top of Dagger.

With dependency injection, a class receives what it needs through its constructor instead of reaching out to a global object itself.

This follows dependency inversion, one of the software development principles grouped under SOLID. Code that calls a singleton directly depends on one concrete implementation instead of an abstraction, which is exactly what that principle argues against.

  • A singleton call site is hardwired to one specific object, everywhere it appears in the codebase
  • A constructor-injected dependency can be swapped for a fake inside a single test file

Koin is a lighter alternative to Dagger and Hilt. It uses runtime lookups instead of compile-time code generation, so you give up some safety in exchange for an easier learning curve.

Memory Leaks and Global State in Android

This is the mistake Android developers make most often. A singleton lives as long as the app does. If it holds a reference to something screen-scoped, that object can’t be garbage collected.

Android’s performance documentation walks through this exact failure. An app-scoped singleton references an object that holds a direct, strong reference to an Activity, so the Activity stays in memory long after it should have been destroyed.

You usually notice it after rotating the screen a few times. Each rotation leaks another Activity instance, and memory keeps climbing. The singleton lives for the whole app, but the Activity only lives for one screen.

The fix is to store the application context in the singleton instead of an Activity context. Pass any screen-scoped data in as a method parameter instead of storing it.

Global mutable state causes the same kind of problem. Once several unrelated features write to the same singleton, finding which one caused a given bug turns into guesswork.

Singleton Behavior Under Serialization

Serialization breaks the one-instance guarantee unless the singleton is written to handle it.

Deserializing an object skips its constructor entirely. The JVM allocates a new instance straight from the byte stream, so a second copy of a supposedly single-instance object can sit in memory next to the original.

JetBrains considers this serious enough to flag automatically. According to JetBrains’ Inspectopedia documentation, any Kotlin object that implements Serializable without a readResolve method triggers a built-in IDE inspection.

The readResolve method tells deserialization to return the existing instance instead of the newly allocated one. Kotlin’s object declarations don’t implement Serializable on their own, so this risk only appears once a developer adds that interface by hand.

Most singletons hold in-memory app state that never crosses a serialization boundary, so for them this never comes up.

FAQ on What Are Kotlin Singleton Classes

Is a Kotlin object declaration the same as a class?

No. You can instantiate a class many times, and it takes a constructor. An object declaration is a single, compiler-managed instance with no constructor at all. Both compile to a JVM class file, but only a class can produce multiple objects.

Is a Kotlin object the same as a Java static class?

Not quite. A Java static utility class only groups static methods and blocks instantiation completely. A Kotlin object is a real, instantiated object, created once and lazily, so it can implement interfaces and hold state. A static class can’t do either.

Can a Kotlin singleton class take constructor parameters?

No, object declarations never accept constructor arguments. Values that depend on outside input go inside an init block, or you set them right after startup through a dedicated initializer function. Kotlin gives singletons no parameterized entry point by design.

Does Kotlin ever create more than one instance of a singleton?

Rarely, but it can happen. Each class loader initializes its own copy. A singleton loaded by two different class loaders, which is common in plugin systems or multi-module setups, produces two separate instances that never see each other’s state.

Can you reset or clear a Kotlin singleton instance?

Not directly. The object keyword has no built-in reset method, because the compiler assumes one instance for the whole run. If you need a resettable value, put that state in a separate object that the singleton holds and can replace.

What Should You Check First in What Are Kotlin Singleton Classes?

Look at the state first. If it varies per user or per request, it doesn’t belong in an object. Also make sure no Android component holds a reference back to the singleton.

Then check ownership. One module should clearly own the singleton’s lifetime. Two modules that both act like they own it is how you end up with bugs nobody can track down.

If those checks pass, you give up a little setup flexibility and get a guarantee the class loader enforces on its own, no locking code required.

Multiplatform support is newer. Kotlin Multiplatform reached Stable status with the 1.9.20 release in November 2023 (JetBrains, 2023). That built on Kotlin/Native’s new memory model, which became the default in the 1.7.20 release in September 2022 (JetBrains, 2022) and relaxed the cross-thread sharing rules that used to limit singletons under the old model. The change is most visible today in how Kotlin coroutines coordinate work across those same shared objects.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.