Companion object in Kotlin: what it is, syntax and usage

Author: IT Sectr Published: 2026-06-21 Reading time: 9 min

companion object is a Kotlin language mechanism that replaces the Java static keyword for declaring static class members. Kotlin has no built-in static — instead, it uses an object declaration with the companion modifier inside a class. According to JetBrains, 2025, companion object allows calling methods and properties through the class name, making them a full equivalent of static members in the Java ecosystem.

Key Takeaways

  • companion object is an object declaration inside a class with the companion keyword, replacing Java static
  • Static members are accessible through the class name without creating an instance: ClassName.method()
  • Named companion object allows giving a name to the object for more readable code
  • Interfaces in Kotlin can also contain a companion object with method implementations
  • @JvmStatic is an annotation for exporting companion object methods as real static methods in Java

What is companion object in Kotlin?

companion object is a special kind of object declaration in Kotlin that is marked with the companion keyword. It allows declaring members that belong to the class rather than its instances — that is, static members in Java terms. Unlike Java, where static is a modifier for individual fields and methods, in Kotlin static members are grouped inside a single companion object.

Any class in Kotlin can contain exactly one companion object. Members of this object are accessible through the class name without creating an instance: MyClass.method(). In terms of bytecode, a companion object is compiled into a separate inner class, and its methods become static methods of the outer class when using the @JvmStatic annotation.

According to Google I/O 2024, companion object has become the main pattern for factory methods, constants, and utility functions in modern Android applications written in Kotlin. Developers choose it over utility classes with package-level functions because companion object maintains a logical connection with the owning class.

Use companion object for grouping static context — constants, factories, helper methods that logically belong to the class but do not require an instance.

Companion object syntax: declaration and access

The basic syntax of companion object is simple: the companion keyword is placed before the object declaration inside the class. If no name is specified, the object gets the name Companion, which can be accessed explicitly or implicitly.

kotlin
class User {
    companion object {
        const val TABLE_NAME = "users"

        fun create(name: String): User {
            return User(name)
        }
    }

    val name: String
    constructor(name: String) { this.name = name }
}

// Access via class name
val table = User.TABLE_NAME
val user = User.create("Alice")

In the example above, TABLE_NAME and create() are accessible via User.TABLE_NAME and User.create(). Note the const modifier for primitive constants — it guarantees compile-time value inlining, similar to Java static final for primitives and String.

Implicit and explicit access via Companion

If a companion object has no name, it can be accessed via the automatic name Companion: User.Companion.TABLE_NAME. In practice this is rarely needed, but it is useful when calling from Java code or when using reflection.

kotlin
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")

// Getting reference to companion object
val companion: User.Companion = User

Named companion object and features

A companion object can have a name, which improves code readability and allows accessing it by a meaningful name. Factory is the most common name for a companion object used as a factory.

kotlin
class HttpResponse {
    companion object Factory {
        fun ok(body: String): HttpResponse = HttpResponse(200, body)
        fun notFound(): HttpResponse = HttpResponse(404, "Not Found")
        fun serverError(): HttpResponse = HttpResponse(500, "Internal Error")
    }
}

// Access via Factory name
val response = HttpResponse.Factory.ok("data")

A named companion object helps document the purpose of static members. In refactoring, the name makes the code self-documenting — a developer instantly sees that Factory creates instances of HttpResponse.

Limitation: exactly one companion object per class

A class can contain only one companion object. This is a fundamental difference from Java, where you can declare any number of static fields and methods without grouping. If you need multiple logical groups of static members, use nested object declarations without companion.

According to the official Kotlin documentation (Kotlin Docs, 2025), the one-companion-object limitation is motivated by language design: static members should be tightly coupled with the class, and one companion object provides a clear boundary for this connection. If there are too many static members, it is a signal to refactor the class.

Companion object in interfaces

In Kotlin, interfaces can also contain a companion object. This allows defining static methods and constants directly inside an interface, which is not possible in Java. This approach is often used for declaring constants related to the interface or factory methods.

kotlin
interface ApiService {
    companion object {
        const val BASE_URL = "https://api.example.com"
        const val TIMEOUT_MS = 5000

        fun create(client: OkHttpClient): ApiService =
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .build()
                .create(ApiService::class.java)
    }
}

In this example, BASE_URL, TIMEOUT_MS and create() belong to the ApiService interface, not its implementations. This is convenient: all constants and factory logic for creating an API service instance are located in one place — inside the interface itself.

Interface implementations do not inherit companion object members — they are called only through the interface name: ApiService.BASE_URL.

@JvmStatic and @JvmField: Java compatibility

When calling companion object methods from Java code, by default they are accessible as methods of the nested Companion class, not as static methods of the outer class. To export them as real static methods and fields for Java, use the @JvmStatic annotation for methods and @JvmField for fields.

AnnotationApplicationResult in Java
@JvmStaticCompanion object methodsStatic method: ClassName.method()
@JvmFieldCompanion object fieldsStatic field: ClassName.field
constPrimitives and StringInline constant: compiler substitutes the value
no annotationMethods/fields by defaultCompanion.method() / Companion.field
kotlin
class MathUtils {
    companion object {
        const val PI = 3.14159

        @JvmStatic
        fun square(x: Int): Int = x * x

        @JvmField
        val TAG: String = "MathUtils"
    }
}

// Called in Java as MathUtils.square(5), MathUtils.TAG

Using @JvmStatic is recommended for all public companion object methods that should be accessible from Java code. const is applied only to primitive types and String — for other types use @JvmField.

Practical examples of using companion object

In real projects, companion object is used for several standard scenarios. Factory methods are the most common pattern: instead of multiple constructors with different signatures, named factory methods are used in companion object.

kotlin
sealed class NetworkResult<out T> {
    data class Success<out T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String) : NetworkResult<Nothing>()

    companion object {
        fun loading<T>(): NetworkResult<T> =
            Loading()
    }
}

private class Loading<T> : NetworkResult<T>()

companion object in NetworkResult provides a factory method loading() that creates a Loading instance with the correct generic type. Without this method, you would have to create an instance directly via Loading(), which exposes the internal implementation.

Constants and logging tags

companion object is often used for storing class-specific constants. Constants declared with const val are inlined at compile time, providing zero runtime overhead.

kotlin
class UserRepository {
    companion object {
        private const val TAG = "UserRepository"
        private const val CACHE_SIZE = 100
        private const val DEFAULT_PAGE_SIZE = 20
    }
}

Frequently Asked Questions

How is companion object different from a regular object in Kotlin?

A regular object is a full-fledged singleton that exists independently of the class. companion object is an object inside a class whose members are called through the outer class name. In bytecode, a companion object becomes a static nested class, while a regular object becomes an autonomous singleton class.

Can companion object be inherited?

No, companion object is not inherited by subclasses. If class B extends class A, then B.method() will not call the method from class A's companion object — you need to call A.method(). This matches the behavior of static methods in Java.

How to call companion object from Java code?

By default — via ClassName.Companion.method(). To call it as a static method, add @JvmStatic to the companion object method. For fields, use @JvmField or declare them with const val for primitives.

Why is there no static keyword in Kotlin?

Kotlin developers decided to abandon static in favor of companion object for uniform work with objects. static in Java violates OOP principles because static methods are not tied to an instance. companion object is a first-class object that can be passed around, extended with extension functions, and implement interfaces.

Does companion object affect performance?

When using const val for primitives and String — zero overhead, the value is inlined into bytecode. For methods, there is no overhead unless the method is inline. In most cases, companion object does not create any measurable performance impact. Use @JvmStatic only for public API called from Java.

Summary

  • companion object is a Kotlin mechanism for declaring static class members, replacing Java static
  • One companion object per class — groups all static elements in a single companion object
  • Access via the class name: ClassName.method() or ClassName.Companion.method()
  • @JvmStatic and @JvmField export members as real static methods and fields for Java
  • const val for primitives and String — inlines the value at compile time with zero overhead
  • Interfaces also support companion object for constants and factory methods
  • Named companion object improves code readability and self-documents the purpose of static members

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also