Question
How can I store a class instance in a private or public static variable in Kotlin and access it through a static-style method?
For example, the following Java class stores the first created Foo object in a static field and returns it from a static method:
public class Foo {
private static Foo instance;
public Foo() {
if (instance == null) {
instance = this;
}
}
public static Foo get() {
return instance;
}
}
What is the Kotlin equivalent, and what is the idiomatic Kotlin approach for a single shared instance?
Short Answer
Kotlin has no static keyword. Instead, use a companion object for members associated with a class rather than an individual object. For a class that must have exactly one shared instance, Kotlin's object declaration is usually the simpler and safer choice.
Concept
Kotlin does not use Java-style static members. A property or function normally belongs to an instance:
val first = Foo()
val second = Foo()
Each object can have its own instance properties. However, sometimes a value belongs to the class itself, not to any one object. Examples include a shared counter, a factory function, or a constant.
Use a companion object to declare these class-level members:
class Foo {
companion object {
var count = 0
}
}
Access it using the class name:
Foo.count += 1
A companion object is a real singleton object attached to its containing class. This is why it can contain properties and functions.
For the specific goal of keeping one globally shared Foo instance, Kotlin provides the object declaration. It creates one instance automatically and safely, so you do not need a nullable instance variable or constructor checks.
Mental Model
Think of a class as a blueprint for houses.
- An ordinary property, such as
address, belongs to each house built from the blueprint. - A companion object is like a shared office next to the blueprint: it stores information and tools available to every house, such as a count of houses built.
- An
objectdeclaration is like building exactly one named building. Kotlin creates it once and lets everyone use that same building.
Use a companion object when a class needs class-level helpers or shared state. Use object when the thing itself should exist only once.
Syntax and Examples
A companion object is written inside a class:
class Foo {
companion object {
private var instance: Foo? = null
fun get(): Foo? {
return instance
}
}
init {
if (instance == null) {
instance = this
}
}
}
Usage:
val first = Foo()
val savedInstance = Foo.get()
println(savedInstance === first) // true
Foo? means that the value may be null. It is necessary here because no Foo object exists before the first Foo() call.
However, this design still allows callers to create more objects:
val first = Foo()
val second = Foo()
println(first === second) // false
println(Foo.get() === first) // true
If only one instance should ever exist, use object instead:
object Foo {
fun greet(): String = "Hello from the only Foo"
}
fun main() {
println(Foo.greet())
}
There is no Foo() call. Access the single object directly as Foo. Kotlin initializes an object lazily and safely when it is first used.
Step by Step Execution
Consider this companion-object version:
class Session {
companion object {
private var current: Session? = null
fun currentSession(): Session? = current
}
init {
if (current == null) {
current = this
}
}
}
fun main() {
val first = Session()
val second = Session()
println(Session.currentSession() === first)
println(Session.currentSession() === second)
}
Execution:
- Kotlin loads the
Sessionclass and its companion object.currentstarts asnull. Session()createsfirst.- The
initblock runs forfirst. Sincecurrentisnull, it storesfirstincurrent. Session()createssecond.- The
initblock runs again.currentis no longernull, so it stays unchanged. Session.currentSession()returnsfirst.- The first comparison prints
true; the second printsfalse.
This shows why the pattern remembers the first instance but does not prevent later instances from being constructed.
Real World Use Cases
Companion objects are commonly used for class-level behavior:
- Factory functions: create objects through named functions such as
User.fromJson(json). - Constants: keep values related to a class, such as
HttpStatus.NOT_FOUND. - Parsing and conversion: provide functions like
Money.parse("12.50"). - Shared counters: count how many instances have been created.
- Lookup helpers: retrieve predefined values by an identifier.
Use an object declaration for a true single shared value:
- Application configuration loaded once.
- A stateless formatter or validator.
- A central registry in a small application.
- A simple in-memory cache.
For Android and server applications, be careful with global mutable state. Dependencies such as databases, network clients, and repositories are often managed by a dependency-injection framework rather than accessed as global singletons.
Real Codebase Usage
Factory methods in a companion object
Factories make object creation clearer than exposing several constructors:
class Email private constructor(val value: String) {
companion object {
fun parse(value: String): Email? {
return if ("@" in value) Email(value) else null
}
}
}
val email = Email.parse("ada@example.com")
Constants
Use const val for compile-time constants of primitive or String types:
class ApiClient {
companion object {
const val DEFAULT_TIMEOUT_MS = 5_000
}
}
Guard clauses for validation
A factory can reject invalid input early:
class Percentage private constructor(val value: Int) {
companion object {
fun of(value: Int): Percentage {
require(value in 0..100) { "Percentage must be from 0 to 100" }
return Percentage(value)
}
}
}
Singleton services
For a stateless utility or a single shared service, object is concise:
object SlugFormatter {
fun format(text: String): String {
return text.trim().lowercase().replace(" ", "-")
}
}
Java interoperability
Kotlin code calls companion functions as Foo.create(). If Java callers need a true static method, add @JvmStatic:
class IdGenerator {
companion object {
@JvmStatic
fun nextId(): String = java.util.UUID.randomUUID().toString()
}
}
From Java, this can be called as IdGenerator.nextId(). The annotation is mainly for Java interoperability, not normal Kotlin code.
Common Mistakes
Using static
Kotlin does not support the Java static keyword.
// Does not compile
class Foo {
static var instance: Foo? = null
}
Put class-level members inside a companion object instead.
class Foo {
companion object {
var instance: Foo? = null
}
}
Forgetting nullable types
This is invalid because null cannot be assigned to a non-null Foo:
// Does not compile
private var instance: Foo = null
Use Foo? when the value can be absent:
private var instance: Foo? = null
Force-unwrapping a nullable instance
The !! operator throws NullPointerException if no instance exists yet:
fun get(): Foo = instance!!
Prefer returning Foo?, or design the class so an instance is always available with object.
Thinking the constructor pattern enforces one instance
This code saves only the first instance, but it still permits more instances:
val one = Foo()
val two = Foo()
If exactly one instance is required, use object Foo or a private constructor plus a controlled factory.
Storing shared mutable state without considering concurrency
Multiple threads can read and update a mutable companion property at the same time. For shared application state, use appropriate synchronization, thread-safe structures, or a design that avoids global mutation.
Using @JvmStatic unnecessarily
Kotlin callers do not need @JvmStatic. Add it only when Java API ergonomics require actual static-style access.
Comparisons
| Feature | companion object | object declaration | Instance member |
|---|---|---|---|
| Belongs to | A class | One singleton object | Each created object |
| Can create class instances? | Yes | Not applicable; it is the instance | Already belongs to an instance |
| Typical purpose | Factories, constants, class helpers | One shared service or value | Object-specific state and behavior |
| Access syntax | User.create() | AppConfig.load() | user.name |
| Can prevent multiple instances? | Not by itself | Yes | No |
val versus var in shared properties
| Keyword | Meaning | Use it when |
|---|---|---|
val | Reference cannot be reassigned | The shared value should not change |
var | Reference can be reassigned | The shared value must be replaced or updated |
A val does not automatically make the referenced object immutable. It only prevents assigning a different object to that property.
Named versus unnamed companion objects
Both forms work:
class User {
companion object {
fun createGuest() = User()
}
}
class Product {
companion object Factory {
fun createDefault() = Product()
}
}
In ordinary Kotlin use, both can be called as User.createGuest() and Product.createDefault(). Naming a companion is useful when you need to refer to the companion object itself.
Cheat Sheet
class MyClass {
companion object {
const val MAX_ITEMS = 10
private var saved: MyClass? = null
fun create(): MyClass = MyClass()
}
}
val item = MyClass.create()
println(MyClass.MAX_ITEMS)
// Exactly one instance
object AppSettings {
var darkMode: Boolean = false
}
AppSettings.darkMode = true
- Kotlin has no
statickeyword. - Put class-level properties and functions in
companion object. - Use
objectfor an idiomatic singleton. - Use
Foo?for a value that can be absent (null). - Use
const valonly for compile-time primitive orStringconstants. @JvmStaticis primarily for Java callers.- A constructor that stores the first instance does not stop callers from creating more instances.
FAQ
Does Kotlin have static variables?
Kotlin has no static keyword. Use a property inside a companion object for a class-level variable.
What is the Kotlin equivalent of a static method?
Put the function inside a companion object and call it with the class name, such as User.fromJson(json).
Should I use object or companion object for a singleton?
Use object when the entire type should have exactly one instance. Use a companion object when a normal class needs class-level functions, constants, or factories.
Can I access a companion object function without creating an instance?
Yes:
class MathTools {
companion object {
fun double(value: Int) = value * 2
}
}
val result = MathTools.double(4)
Why is my shared instance type Foo??
It is nullable because it starts without an object assigned. Before the first Foo() is created, the instance is null.
Is a Kotlin object thread-safe?
Kotlin initializes an object safely on its first access. However, properties you mutate inside that object may still require concurrency protection when used from multiple threads.
Can Java call Kotlin companion object functions?
Yes. Java can access the companion object. Add @JvmStatic when you want Java code to call the function as a traditional static method.
Mini Project
Description
Build a small application settings store that keeps one shared set of settings. This demonstrates why an object declaration is often better than manually tracking an instance in a companion object.
Goal
Create a singleton settings store that can update and display an application's theme and language.
Requirements
- Declare a singleton named
AppSettingsusingobject. - Store a
themesetting with a default value of"light". - Store a
languagesetting with a default value of"en". - Add a function that updates both settings.
- Add a function that returns a readable settings summary.
- Access the singleton from
mainwithout constructing it.
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.