What Are Kotlin Singleton Classes? Explained

Summarize this article with:

Java developers write 15 to 25 lines of boilerplate just to create a single instance of a class. Kotlin does it in one line. If you’ve been wondering what are Kotlin singleton classes and why the object keyword keeps showing up in every codebase you read, this article breaks it all down.

Kotlin’s approach to the singleton design pattern removes the thread safety headaches, the double-checked locking, and the private constructors that made Java singletons so error-prone. The compiler handles all of it.

This guide covers how object declarations work at the bytecode level, how they compare to Java singleton implementations, how companion objects fit in, and where singletons create real problems in Android and multiplatform projects. You’ll also see when to use them and when to reach for dependency injection instead.

What Is a Kotlin Singleton Class

maxresdefault What Are Kotlin Singleton Classes? Explained

A Kotlin singleton class is a class that allows only one instance to exist throughout the entire application lifecycle. That single instance gets shared globally, and every part of the program accesses the same object.

Most languages force you to write boilerplate to pull this off. Kotlin doesn’t. The language has a built-in object keyword that handles singleton creation at the compiler level.

When you declare something as an object in Kotlin, the compiler does all the heavy lifting. It generates a class with a private constructor, creates a static INSTANCE field, and initializes it through a static block. You get a thread-safe, lazily loaded singleton without writing a single line of synchronization code.

The singleton design pattern itself comes from the Gang of Four’s Design Patterns book, published in 1994. It restricts class instantiation to a single object, providing a global access point. Common uses include database connection pools, logging services, caches, and configuration managers.

Google estimates that 70% of the top 1,000 apps on the Play Store are written in Kotlin (Dice, 2024). JetBrains puts that number even higher, claiming 95% of those apps contain some Kotlin code.

That kind of adoption means the object keyword isn’t some niche feature. It’s running in production across millions of devices right now.

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 →

Here’s what a basic Kotlin singleton looks like:

object DatabaseConfig { val url = "jdbc:mysql://localhost:3306/mydb" val maxConnections = 10

fun connect() { // connection logic } } `

That’s it. No getInstance() method. No private constructor you have to remember to write. No double-checked locking. Just the object keyword and you're done.

How the Object Keyword Works in Kotlin

maxresdefault What Are Kotlin Singleton Classes? Explained

The object keyword replaces all of the manual singleton boilerplate that Java developers have been writing for decades. You declare it, and the Kotlin compiler generates the rest.

According to Kotlin’s official documentation, the initialization of an object declaration is thread-safe and done on first access. The JVM’s class loading mechanism handles the synchronization automatically, so you don’t need volatile fields or synchronized blocks.

Syntax and Compiled Output

Declaration structure: You use object where you'd normally use class. No constructor parameters allowed.

` object Analytics { private var eventCount = 0

fun trackEvent(name: String) { eventCount++ println(“Event #$eventCount: $name”) } } `

When the Kotlin compiler processes this, it generates JVM bytecode equivalent to something like:

` public final class Analytics { public static final Analytics INSTANCE;

static { Analytics instance = new Analytics(); INSTANCE = instance; }

private Analytics() {} } `

A static final field holds the single instance. A static initializer block creates it. The constructor is private. That’s the standard pattern the JVM uses for class-level initialization, and it’s been thread-safe since Java was created.

You can verify this yourself in IntelliJ IDEA. Go to Tools, then Kotlin, then Show Kotlin Bytecode, and hit Decompile. Took me a while to start checking bytecode regularly, but once you do, a lot of Kotlin’s “magic” makes more sense.

Object Declarations vs Object Expressions

This is where people get tripped up. Kotlin uses the object keyword in two different ways, and they do completely different things.

FeatureObject DeclarationObject Expression
Creates a singletonYesNo
Has a nameYesAnonymous
Created whenFirst accessImmediately at point of use
Typical useShared state, utilitiesInline interface implementations

Object declarations create named singletons that persist across the application. They’re what you use for things like configuration holders or service locators.

Object expressions create anonymous, one-off instances. Think of them as Kotlin’s version of Java anonymous inner classes. You use them when you need to implement an interface on the spot without creating a separate class.

` // Object expression - NOT a singleton val listener = object : View.OnClickListener { override fun onClick(v: View?) { // handle click } } `

Every time that code runs, it creates a new object. That’s the key difference. If you mix these up in your head (and I’ve seen it happen on code reviews), you’ll end up confused about why your “singleton” has multiple instances.

Companion Objects as Partial Singletons

maxresdefault What Are Kotlin Singleton Classes? Explained

A companion object is a singleton that lives inside another class. It’s how Kotlin handles things that Java would put in static methods and fields.

You can’t use the static keyword in Kotlin. It just doesn't exist. Instead, you put shared class-level behavior inside a companion object block, and the compiler takes care of making it accessible at the class level.

` class User private constructor(val name: String) { companion object { fun create(name: String): User { return User(name) } } } `

Calling User.create(“Alice”) looks exactly like calling a static method in Java. But under the hood, it's an instance method on a singleton companion object.

How the Companion Gets Compiled

Bytecode output: The compiler creates a nested class called Companion with a static field referencing the single instance.

This means companions can do things that Java static methods can’t. They can implement interfaces. They can be passed around as objects. That flexibility is something you don’t get with plain static.

Java interop note: If you’re calling Kotlin companion members from Java code, you’ll need to go through User.Companion.create(“Alice”) unless you add the @JvmStatic annotation. Adding @JvmStatic tells the compiler to generate an additional static method on the outer class, so Java callers can just write User.create(“Alice”) like they'd expect.

A JetBrains Developer Ecosystem survey found that 55% of Kotlin developers chose Kotlin as their main language, showing that companion objects and other Kotlin-specific patterns are now mainstream rather than edge cases.

Kotlin Singleton vs Java Singleton Pattern

Java requires you to make decisions that Kotlin has already made for you. Eager or lazy initialization? Double-checked locking or enum-based? Synchronized or volatile? Every choice adds complexity and room for bugs.

Kotlin’s object keyword compresses all of that into one line.

Java’s Singleton Options

Eager initialization: Instance created at class load time. Simple but wastes resources if the singleton is never used.

Lazy with double-checked locking: The “classic” approach. Requires volatile, two null checks, and a synchronized block. Took years for the Java community to agree on the correct implementation, and people still get it wrong.

Enum-based: Joshua Bloch’s recommendation from Effective Java. Handles serialization and reflection attacks automatically. But it feels weird to model a database connection pool as an enum. Your mileage may vary.

Bill Pugh (holder pattern): Uses a static inner class to defer initialization. Thread-safe through the JVM classloader. Probably the closest analog to what Kotlin generates.

Side-by-Side Comparison

ConcernJava (manual)Kotlin (object)
Lines of code15–25 lines3–5 lines
Thread safetyDeveloper responsibilityHandled by compiler + JVM
SerializationRequires readResolve()Not automatically handled
Reflection attacksVulnerable (except enum)Still possible but rare in practice
Constructor paramsSupportedNot allowed

An NDepend analysis of 100 open-source codebases found that 49 out of 100 projects used singletons, and codebases with stateful singletons showed 40% higher average method complexity compared to those without singletons. That’s a pattern-level problem, not a language-level one. But Kotlin at least removes the implementation complexity so you can focus on whether a singleton is the right design choice in the first place.

Companies like Netflix, Pinterest, and Uber have moved their Android apps from Java to Kotlin. A big part of that decision comes down to reduced boilerplate, and the singleton pattern is one of the clearest examples of that reduction.

Thread Safety and Initialization in Kotlin Singletons

maxresdefault What Are Kotlin Singleton Classes? Explained

Kotlin’s object declarations are thread-safe by default. This isn't some Kotlin-specific trick. It's a direct result of how the JVM handles class initialization.

The JVM specification guarantees that a class’s static initializer runs exactly once, and it uses internal locking to make sure no other thread can access the class until initialization completes. Since Kotlin compiles object declarations into classes with static initializer blocks, you get thread safety for free.

When Initialization Happens

First access triggers creation. The singleton instance doesn’t exist at application startup. It gets created the first time any code touches the object, whether that’s calling a function, reading a property, or even referencing the class itself.

This lazy loading behavior matters in Android development, where startup time directly affects user experience. If a singleton initializes a database connection or loads a large configuration file, you want that to happen when it’s actually needed, not when the app boots.

What Thread Safety Covers (and Doesn’t)

There’s an important distinction here that gets overlooked. The creation of the singleton is thread-safe. The operations on the singleton are not automatically safe.

` object Counter { var count = 0 // NOT thread-safe to mutate

fun increment() { count++ // race condition if called from multiple threads } } `

Two threads calling increment() at the same time can still produce incorrect results. The JVM guarantees one Counter instance exists, but it doesn't synchronize your methods for you.

If you need thread-safe state inside a singleton, you still need Kotlin coroutines with Mutex, AtomicInteger, or explicit synchronization. The object keyword solves the initialization problem, not the concurrent access problem.

The JetBrains Developer Ecosystem 2024 survey shows that 75% of Kotlin users express satisfaction with the language. Part of that satisfaction probably comes from not having to debug double-checked locking implementations at 2 AM anymore.

Using Kotlin Singletons in Android Development

maxresdefault What Are Kotlin Singleton Classes? Explained

Android is where Kotlin singletons show up most in production code. Retrofit clients, Room database instances, SharedPreferences wrappers, analytics trackers. All of these tend to be singletons.

According to JetBrains, 60% of professional Android developers use Kotlin as their primary language, and Kotlin holds a dominant 70-75% market share in the Android ecosystem as of 2025. That means the object keyword is the default singleton mechanism for most Android apps being built right now.

Common Singleton Patterns in Android

Network client: A Retrofit instance configured once and reused across the app.

` object ApiClient { val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() } `

Database instance: Room databases should have exactly one instance per app. A singleton prevents the overhead of opening multiple database connections.

Feature flags: A singleton that loads remote configuration values once and exposes them throughout the app.

The Context Leak Problem

This is the number one mistake I’ve seen in Android projects that use object singletons. Storing a reference to an Activity's Context inside a singleton creates a memory leak because the singleton outlives the Activity.

` // DON'T do this object AppManager { lateinit var context: Context // leaks if Activity context } `

Always use Application context, never Activity context, inside a singleton. Or better yet, use dependency injection frameworks like Hilt or Koin that manage scope for you.

Singleton Scope in Dependency Injection Frameworks

DI frameworks give you the singleton behavior without the tight coupling that comes with a plain object declaration.

FrameworkSingleton MechanismKey Difference from object
Dagger/Hilt@Singleton scopeConstructor injection, testable
Koinsingle { } definitionRuntime resolution, less boilerplate
Plain Kotlinobject keywordCompile-time, no external library

Google Workspace adopted Kotlin Multiplatform to share business logic between Android and iOS, keeping a single source of truth for features like Gmail, Docs, and Calendar. At that scale, plain object singletons don't cut it. You need DI-managed scopes to keep things testable and maintainable across teams.

For smaller apps or quick prototypes built during rapid app development cycles, though, a simple object singleton is perfectly fine. Don't over-engineer what doesn't need it.

Stone, a Brazilian fintech company, reported 40% faster feature delivery after adopting Kotlin Multiplatform with shared singletons and business logic across platforms (KotlinConf 2025). That kind of result doesn’t come from the singleton pattern alone, but it shows how Kotlin’s concise design patterns contribute to real productivity gains in the software development process.

Limitations and Drawbacks of Kotlin Singleton Classes

The object keyword makes creating singletons easy. Maybe too easy. And that convenience has a cost when your project grows beyond a handful of screens.

An NDepend study of 100 open-source codebases found that projects with stateful singletons had types with more than double the coupling compared to singleton-free projects (afferent coupling scores of 6.86 vs 3.35). The pattern itself creates structural pressure toward tight coupling, regardless of language.

Testing Is the Biggest Pain Point

Kotlin object declarations are final by default. No subclassing, no swapping in a test double through constructor injection. You can't just pass a mock the way you would with a regular class.

MockK solves most of it. The library provides mockkObject() which intercepts calls to a singleton and lets you stub return values. But that's a library-specific workaround, not a feature of the language.

` mockkObject(Analytics) every { Analytics.trackEvent(any()) } returns Unit

// test your code verify { Analytics.trackEvent(“purchase”) }

unmockkObject(Analytics) `

If your team uses Mockito instead of MockK, mocking singleton objects gets significantly harder. You’ll likely need PowerMock or a delegate pattern to work around it.

No Constructor Parameters

Kotlin object declarations don't allow constructors. Period. If your singleton needs external configuration at creation time (a database URL, an API key, a Context), you can't pass it through the constructor.

Common workarounds:

  • An init() function called early in the app lifecycle
  • A lateinit var set before first use
  • Kotlin’s lazy delegation with LazyThreadSafetyMode.SYNCHRONIZED

None of these are as clean as constructor injection. They push initialization responsibility onto the caller and introduce the possibility of accessing an uninitialized singleton at runtime.

Global Mutable State

A singleton with var properties is global mutable state. Any part of the app can read or write to it, and tracing where a value changed becomes a debugging headache in larger codebases.

GeeksforGeeks classifies the singleton as an anti-pattern when overused, specifically citing hidden dependencies and scaling issues as the primary risks. The pattern works well for stateless utilities. It gets tricky fast when you start storing mutable data in it.

If you catch yourself adding more than two or three mutable properties to an object, that's usually a sign you should look into code refactoring toward a DI-managed class instead.

Kotlin Singleton in Multiplatform Projects

maxresdefault What Are Kotlin Singleton Classes? Explained

Kotlin Multiplatform (KMP) lets you share business logic across Android, iOS, desktop, and web from a single codebase. Singletons defined with the object keyword work in shared KMP modules, but their behavior varies depending on the compilation target.

KMP usage jumped from 7% to 18% among developers in just one year, according to JetBrains’ Developer Ecosystem survey data (2024 to 2025). That kind of growth means more teams are writing shared singleton objects that need to work across platforms.

How Object Declarations Compile Per Target

TargetCompilationSingleton Behavior
JVM (Android)Java bytecodeStatic initializer, classloader-safe
Native (iOS)LLVM native binaryGlobal variable, frozen by default (old model)
JS (Web)JavaScriptModule-level object, single-threaded
WasmWebAssemblySimilar to JS, single-threaded context

On the JVM, object declarations rely on the classloader for thread-safe initialization. On Kotlin/Native (which powers iOS builds), the mechanism is different. The old memory model froze objects across threads. The new memory model (default since Kotlin 1.7.20) removed that freezing requirement, but you still need to handle concurrent access yourself.

Using expect/actual with Singletons

Platform-specific singletons use Kotlin’s expect/actual mechanism when different targets need different implementations.

` // Common code expect object PlatformLogger { fun log(message: String) }

// Android actual actual object PlatformLogger { actual fun log(message: String) = Log.d(“App”, message) }

// iOS actual actual object PlatformLogger { actual fun log(message: String) = NSLog(message) } `

Forbes shares over 80% of its logic across iOS and Android using KMP, according to JetBrains case studies. That level of code sharing includes shared singleton objects for networking, configuration, and data caching.

McDonald’s expanded KMP from a single payments feature to their entire mobile application, reporting fewer crashes and better performance across both platforms after launch (KotlinConf 2025).

The KMP Survey Q2 2024 found that 65% of teams reported improved performance and quality after adopting Kotlin Multiplatform. Shared singletons for things like API integration layers and feature flag management contribute directly to that consistency across platforms.

Common Patterns That Use Kotlin Singletons

maxresdefault What Are Kotlin Singleton Classes? Explained

Singletons aren’t just a textbook concept. They show up in specific, recurring patterns across production Kotlin code. Some of these make sense. Others have been replaced by better alternatives, and sticking with the singleton version is more habit than design.

Logger Instances

Logging is probably the most common and least controversial use of a singleton in any language. You want one logger, configured once, writing to one output stream.

` object AppLogger { fun info(tag: String, msg: String) = println("INFO [$tag]: $msg") fun error(tag: String, msg: String) = println("ERROR [$tag]: $msg") } `

Why it works: Loggers are typically stateless or append-only. No mutable state contention, no initialization dependencies, no reason to create multiple instances. This is the one singleton use case that even singleton critics tend to accept.

Configuration Holders and Feature Flags

A singleton that loads remote config values once and exposes them as read-only properties throughout the app. H&M built a shared feature flag library using KMP to unify experimentation logic across Android and iOS.

Remote config singletons work well when the data is loaded once at startup and rarely changes. They break down when you need per-user or per-session flags, because then you’re dealing with mutable state in a global scope.

Service Locator Pattern

The service locator is basically a singleton registry that holds references to other objects. You ask it for a dependency by type, and it hands one back.

  • Simple to implement with a Kotlin object
  • No external library needed
  • Gets messy once you have 15+ registered services

Most teams outgrow this quickly. At that point, a real DI framework like Hilt or Koin gives you scoped lifecycle management and constructor injection that the service locator can’t match. Still, for prototypes and small utilities, a service locator object can save you from over-engineering.

Event Buses (Mostly Replaced)

Singleton event buses were everywhere a few years ago. Libraries like EventBus by greenrobot used a global singleton to publish and subscribe to events across an app’s components.

These days, Kotlin flows and shared StateFlow/SharedFlow have largely replaced event buses for reactive communication. They give you typed, lifecycle-aware streams without the debugging headaches of a global event singleton where anything can publish anything.

Cache and Registry Implementations

In-memory caching is another classic singleton use. One cache instance shared across the app, holding frequently accessed data so you’re not hitting the network or database on every call.

PatternSingleton FitBetter Alternative When…
LoggerExcellentRarely needs replacing
Config holderGoodConfig varies per user/session
Service locatorOkay for small appsApp has 10+ dependencies
Event busOutdatedUse Kotlin flows instead
CacheGoodNeed distributed or scoped caching

Duolingo increased its shipping speed and developer confidence by expanding KMP with shared business logic singletons across their mobile apps (KotlinConf 2025). The key was being selective. They shared the patterns that made sense (networking, data models) and kept platform-specific code native.

The right question isn’t “should I use a singleton?” It’s “does this specific case actually need exactly one instance with global access?” If the answer is yes, Kotlin’s object keyword gives you the cleanest path to get there. If you're not sure, lean toward a class with unit testing in mind. You can always scope it as a singleton later through your DI framework without changing the class itself.

FAQ on What Are Kotlin Singleton Classes

What is a singleton class in Kotlin?

A singleton class in Kotlin is a class restricted to a single instance throughout the application. You declare it using the object keyword, and the compiler generates thread-safe initialization automatically. No manual boilerplate required.

How do you create a singleton in Kotlin?

Use the object keyword followed by a name. You can add properties and functions inside it just like a regular class. The Kotlin compiler handles the private constructor and static instance field behind the scenes.

Is Kotlin object thread-safe?

Yes. The JVM’s class loading mechanism guarantees that the singleton instance is created safely during static initialization. But the methods and mutable properties inside the object still need their own synchronization if accessed from multiple threads.

What is the difference between object and class in Kotlin?

A class can have multiple instances. An object creates exactly one instance, initialized on first access. Objects also don't allow constructors, while classes support both primary and secondary Kotlin constructors.

Can a Kotlin singleton have a constructor?

No. Kotlin object declarations don’t support constructors. If you need to pass parameters during initialization, use a companion object factory method, an init() function, or Kotlin's lazy delegation with synchronized mode.

What is a companion object in Kotlin?

A companion object is a singleton scoped inside another class. It replaces Java’s static keyword. Members are accessed through the class name directly, making it the standard way to define class-level functions and constants in Kotlin.

How do you test a Kotlin singleton?

Use MockK’s mockkObject() function to mock an object declaration in unit tests. This lets you stub return values and verify calls. Always call unmockkObject() afterward to reset state between tests.

What is the difference between object declaration and object expression?

An object declaration creates a named singleton initialized once. An object expression creates an anonymous, disposable instance at the point of use. Expressions are similar to Java anonymous inner classes and don’t persist as singletons.

Do Kotlin singletons work in Multiplatform projects?

Yes. Object declarations compile to platform-specific code on each target. On the JVM they use static initializers. On Kotlin/Native they use global variables. The expect/actual mechanism handles platform-specific singleton implementations.

When should you avoid using a Kotlin singleton?

Avoid singletons when you need testability with constructor injection, when the class holds mutable state accessed by multiple threads, or when your app’s dependency graph is complex enough to benefit from a DI framework like Hilt or Koin.

Conclusion

Understanding what are Kotlin singleton classes comes down to one thing: the object keyword replaces an entire category of boilerplate that Java developers dealt with for decades. Thread-safe initialization, lazy loading, and global access, all handled by the compiler.

But knowing the syntax is only half of it. The real skill is knowing when a singleton fits and when it doesn’t.

For stateless utilities, loggers, and configuration holders, Kotlin object declarations are the cleanest option available. For anything involving mutable state, complex dependencies, or serious test coverage requirements, a class managed through Hilt, Koin, or Dagger will serve you better long term.

Companion objects fill the gap where Java’s static members used to live. Kotlin Multiplatform extends singleton behavior across JVM, Native, and JS targets through the expect/actual mechanism.

Pick the right tool for each situation. Let the object` keyword do what it does well, and reach for software development best practices like dependency injection when the pattern starts working against you.

50218a090dd169a5399b03ee399b27df17d94bb940d98ae3f8daff6c978743c5?s=250&d=mm&r=g What Are Kotlin Singleton Classes? Explained
Related Posts
Read More

How To Work With Maps In Kotlin

Summarize this article with: ChatGPT Claude Perplexity Grok Kotlin maps are everywhere in Android development. Caches, config objects,…