Question

Static Methods and Variables in Kotlin: companion object Guide

kotlinjavastatic-methods

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 object declaration 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.

Take Quiz

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:

  1. Kotlin loads the Session class and its companion object. current starts as null.
  2. Session() creates first.
  3. The init block runs for first. Since current is null, it stores first in current.
  4. Session() creates second.
  5. The init block runs again. current is no longer null, so it stays unchanged.
  6. Session.currentSession() returns first.
  7. The first comparison prints true; the second prints false.

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.

Take Quiz

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

Featurecompanion objectobject declarationInstance member
Belongs toA classOne singleton objectEach created object
Can create class instances?YesNot applicable; it is the instanceAlready belongs to an instance
Typical purposeFactories, constants, class helpersOne shared service or valueObject-specific state and behavior
Access syntaxUser.create()AppConfig.load()user.name
Can prevent multiple instances?Not by itselfYesNo

val versus var in shared properties

KeywordMeaningUse it when
valReference cannot be reassignedThe shared value should not change
varReference can be reassignedThe 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.

Take Quiz

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 static keyword.
  • Put class-level properties and functions in companion object.
  • Use object for an idiomatic singleton.
  • Use Foo? for a value that can be absent (null).
  • Use const val only for compile-time primitive or String constants.
  • @JvmStatic is 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.

Take Quiz

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

  1. Declare a singleton named AppSettings using object.
  2. Store a theme setting with a default value of "light".
  3. Store a language setting with a default value of "en".
  4. Add a function that updates both settings.
  5. Add a function that returns a readable settings summary.
  6. Access the singleton from main without constructing it.
Take Quiz

Keep learning

Related questions

View more