Considering a move to Kotlin? Coming from a Java background? In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin. While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.

Case 9: You need something that belongs to the class. Not to an instance.

The problem

Sometimes, behavior or data does not belong to an object. It belongs to the type itself.

  • Constants.

  • Factory methods.

  • Utility logic that is conceptually “part of the class”.

In Java, this is easy. Almost too easy.

The Java way

Java has static.

public class User {

    public static final int MAX_NAME_LENGTH = 50;

    public static User anonymous() {
        return new User("Anonymous");
    }

    private final String name;

    public User(String name) {
        this.name = name;
    }
}

Usage:

User user = User.anonymous();
int max = User.MAX_NAME_LENGTH;
  • Clear.

  • Familiar.

  • Unquestioned.

The Kotlin way

Kotlin does not have static.

Instead, it has companion objects.

class User(val name: String) {

    companion object {
        const val MAX_NAME_LENGTH = 50

        fun anonymous(): User =
            User("Anonymous")
    }
}

Usage:

val user = User.anonymous()
val max = User.MAX_NAME_LENGTH
  • Looks similar.

  • Feels similar.

  • But it’s not the same thing.

What is a companion object?

A companion object is:

  • A real object

  • Tied to the class

  • Created once

  • Able to implement interfaces

  • Able to be passed around

In other words: - It’s not a language trick. - It’s an object with a name (even if you don’t give it one).

Why not just static?

Because static breaks object composition.

That usually leads to the obvious question: Why?

Object composition works by wiring behavior through replaceable dependencies. Static breaks object composition because it cannot be substituted. Static methods and fields are globally fixed: they cannot be passed around, mocked, or injected. Once you depend on a static, you are hard-wired to that implementation.

Static creates hidden, global dependencies — which is the opposite of composition.

With companion objects, you can:

  • Inject behavior

  • Mock logic in tests

  • Implement interfaces

  • Keep related logic grouped with the type

Things static simply cannot do.

Java interoperability

From Java, a companion object looks like this:

User.Companion.anonymous();
int max = User.Companion.MAX_NAME_LENGTH;

If that feels verbose, Kotlin has you covered:

class User(val name: String) {

    companion object {
        @JvmStatic
        fun anonymous(): User =
            User("Anonymous")
    }
}

Now Java can call:

User.anonymous();

Why this matters

Kotlin replaces a language keyword with a concept. That concept is more powerful — and more consistent.

You don’t lose statics. You gain objects.

Takeaway

Java answers the question:

“Does this belong to the class?”

Kotlin asks another one:

“What kind of thing is this, really?”

Final note

@JvmStatic tells the Kotlin compiler to generate a real Java static member…

Without it:

class User {
  companion object {
    fun anonymous(): User = User()
  }
}

Java would see: User.Companion.anonymous();

With @JvmStatic Java sees:

User.anonymous();              // generated static method
User.Companion.anonymous();    // still exists
Future direction (Kotlin Dev Day, thank you Jacob!)

Perhaps interesting to know: during Kotlin Dev Day, JetBrains mentioned that in the future companion objects might get a “static sibling” that works almost like Java statics:

class User(val name: String) {
    companion fun anonymous(): User = User("Anonymous")

    companion {
        fun evenMoreAnonymous(): User = User("Anonymous")
    }
}

This is not yet finalized. JetBrains explained that in 95% of public GitHub code, the “object” part of companion objects is not used. The idea is to make the object part optional, as many developers find it confusing.

In short: Kotlin is exploring a way to make companion objects simpler and more accessible, while retaining their power.

shadow-left