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 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.
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.
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.
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.
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")
// Getting reference to companion object
val companion: User.Companion = User
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.
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.
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.
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.
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.
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.
| Annotation | Application | Result in Java |
|---|---|---|
| @JvmStatic | Companion object methods | Static method: ClassName.method() |
| @JvmField | Companion object fields | Static field: ClassName.field |
| const | Primitives and String | Inline constant: compiler substitutes the value |
| no annotation | Methods/fields by default | Companion.method() / Companion.field |
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.
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.
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.
companion object is often used for storing class-specific constants. Constants declared with const val are inlined at compile time, providing zero runtime overhead.
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
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.
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.
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.
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.
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
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.
Read also