Kotlin

Why do we use companion object as a kind of replacement for Java static fields in Kotlin

27 September 2026 · 7 min read

Why do we use companion object as a kind of replacement for Java static fields in Kotlin

When migrating from Java to Kotlin, or even just working in a multi-language environment on the JVM, one of the first syntactic differences developers encounter is Kotlin’s lack of a direct static keyword. In Java, static fields and methods are fundamental for defining class-level members, creating utility functions, or implementing singleton patterns. So, if Kotlin doesn’t have static, how do we achieve similar functionality? The answer lies in the elegant and powerful companion object, which serves as a kind of replacement for Java static fields and methods. This construct is not merely a syntactic sugar; it represents a deliberate design choice by the Kotlin team, reflecting a more object-oriented approach while maintaining excellent interoperability with existing Java codebases. Understanding its purpose and how it functions is crucial for writing idiomatic, maintainable Kotlin.

Understanding Java’s Static Members

In Java, the static keyword allows members (fields or methods) to belong to the class itself, rather than to any specific instance of that class. This means you can access them directly using the class name, without needing to create an object. Common uses for Java static fields include defining constants (e.g., Math.PI), creating utility methods (e.g., Collections.sort()), or implementing factory methods that return instances of the class.

For example, a static factory method might be used to control object creation, ensuring certain conditions are met or returning cached instances. Similarly, static utility methods encapsulate functionality that doesn’t depend on an object’s state, making them widely accessible. While powerful, Java’s static members have some limitations. They cannot be overridden in subclasses, nor can they access non-static members directly. This can sometimes lead to less flexible designs, particularly in highly object-oriented paradigms.

The ubiquity of the static keyword in Java has ingrained its usage deeply into the developer mindset. However, Kotlin’s designers opted for a different path, aiming for a cleaner, more purely object-oriented language. This decision led to the introduction of the companion object, which provides a more flexible and robust alternative for achieving class-level member functionality.

Kotlin’s Approach: The companion object

In Kotlin, the companion object provides a powerful and idiomatic solution for class-level members that would typically be implemented as Java static fields. Unlike Java’s static keyword, which attaches members directly to the class without an object instance, a companion object is a real object – specifically, a singleton instance automatically created by the JVM when its containing class is loaded. This design allows it to hold properties and functions that can be accessed directly using the class name, mimicking the syntax of Java’s static members while retaining the benefits of object-oriented principles.

When you declare a companion object inside a class, it essentially creates a nested object that is unique to that class. You can define properties and functions within this companion object, and then call them using ClassName.memberName, just as you would with Java static fields. This makes the transition from Java quite smooth from a usage perspective. For instance, if you had MyClass.MY_CONSTANT in Java, in Kotlin it would become MyClass.MY_CONSTANT, with MY_CONSTANT defined inside the companion object.

The fact that a companion object is a true object opens up many possibilities that Java’s static members don’t offer. It can implement interfaces, be extended, and even have extension functions defined on it, something impossible for Java’s static methods. This flexibility enhances code organization and allows for more sophisticated design patterns, such as implementing a factory method interface directly on the companion object itself. It’s a sophisticated way to manage class-level data and behavior without compromising Kotlin’s object-oriented philosophy.

Infographic here
Why Not Just `static`? The Design Philosophy --------------------------------------------

The absence of a direct static keyword in Kotlin is a deliberate design choice, rooted in the language’s commitment to a more consistent and flexible object-oriented paradigm. Kotlin aims to avoid some of the complexities and limitations associated with Java’s static members. By replacing Java static fields with a companion object, Kotlin achieves several key advantages:

  • Object-Oriented Consistency: A companion object is a true object, allowing it to implement interfaces, inherit from classes, and be passed around as a value. This aligns better with object-oriented principles, where behavior and data are encapsulated within objects.
  • Enhanced Flexibility: Unlike static methods, members of a companion object can be overridden if the companion object implements an interface. This provides greater flexibility for testing and extending functionality, enabling patterns like dependency injection for class-level services.
  • Better Interoperability: While not directly static, Kotlin’s compiler generates actual static methods on the JVM for companion object members, which are prefixed with the companion object’s name (or Companion if unnamed). This ensures seamless interoperability with Java code, which can call these members just like regular static methods.
  • Clarity and Scoping: Using a companion object explicitly declares that these members are “companions” to the class, providing class-level functionality. This can lead to clearer code organization compared to scattered static methods.

Kotlin’s designers, like many modern language architects, sought to refine the concept of “class-level” functionality. According to insights shared by Kotlin’s lead language designer, Andrey Breslav, the goal was to provide solutions that are powerful yet don’t break the object model. By making the container for these members an actual object, even a singleton, Kotlin ensures that all code operates within an object context, promoting a more uniform and robust programming model. This decision contributes to Kotlin’s reputation for being a pragmatic yet principled language.

Practical Applications and Best Practices

The companion object is incredibly versatile and finds its utility in various scenarios where you’d typically use Java static fields. Let’s explore some common applications Question & Answer :

What is the intended meaning of “companion object”? So far I have been using it just to replace Java’s static when I need it.

I am confused with:

  • Why is it called “companion”?
  • Does it mean that to create multiple static properties, I have to group it together inside companion object block?
  • To instantly create a singleton instance that is scoped to a class, I often write

:

companion object { val singleton by lazy { ... } } 

which seems like an unidiomatic way of doing it. What’s the better way?

  • What is the intended meaning of “companion object”? Why is it called “companion”?

    First, Kotlin doesn’t use the Java concept of static members because Kotlin has its own concept of objects for describing properties and functions connected with singleton state, and Java static part of a class can be elegantly expressed in terms of singleton: it’s a singleton object that can be called by the class’ name. Hence the naming: it’s an object that comes with a class.

    Its name used to be class object and default object, but then it got renamed to companion object which is more clear and is also consistent with Scala companion objects.

    Apart from naming, it is more powerful than Java static members: it can extend classes and interfaces, and you can reference and pass it around just like other objects.

  • Does it mean that to create multiple static properties, I have to group it together inside companion object block?

    Yes, that’s the idiomatic way. Or you can even group them in non-companion objects by their meaning:

    class MyClass { object IO { fun makeSomethingWithIO() { /* ... */ } } object Factory { fun createSomething() { /* ... */ } } } 
    
  • To instantly create a singleton instance that is scoped to a class, I often write /*...*/ which seems like an unidiomatic way of doing it. What’s the better way?

    It depends on what you need in each particular case. Your code suits well for storing state bound to a class which is initialized upon the first call to it.

    If you don’t need it to be connected with a class, just use object declaration:

    object Foo { val something by lazy { ... } } 
    

    You can also remove lazy { ... } delegation to make the property initialize on first class’ usage, just like Java static initializers

    You might also find useful ways of initializing singleton state.