In Kotlin, a companion object is an object declared inside a class that can hold members callable on the class itself, giving you static-like behavior.
Kotlin does not have Java-style static members directly. Instead, you usually use companion object.
Basic example
class User(val name: String) {
companion object {
const val DEFAULT_NAME = "Guest"
fun createDefault(): User {
return User(DEFAULT_NAME)
}
}
}
You can access companion object members using the class name:
fun main() {
println(User.DEFAULT_NAME)
val user = User.createDefault()
println(user.name)
}
Output:
Guest
Guest
Why it feels like static
This:
User.createDefault()
is similar to calling a static method in Java:
User.createDefault();
But internally, Kotlin’s companion object is an actual singleton object associated with the class.
Companion object properties
You can put properties inside a companion object:
class Counter {
companion object {
var count = 0
fun increment() {
count++
}
}
}
Usage:
fun main() {
Counter.increment()
Counter.increment()
println(Counter.count)
}
Output:
2
Named companion objects
A companion object can have a name:
class Database {
companion object Factory {
fun connect(): Database {
return Database()
}
}
}
You can still access members through the class name:
val db = Database.connect()
Or through the companion object name:
val db = Database.Factory.connect()
Factory method example
A common use case is creating factory methods:
class Person private constructor(
val name: String,
val age: Int
) {
companion object {
fun of(name: String, age: Int): Person {
require(age >= 0) { "Age cannot be negative" }
return Person(name, age)
}
}
}
Usage:
fun main() {
val person = Person.of("Alice", 30)
println(person.name)
}
Constants in companion objects
For compile-time constants, use const val:
class ApiConfig {
companion object {
const val BASE_URL = "https://api.example.com"
const val TIMEOUT_SECONDS = 30
}
}
Usage:
println(ApiConfig.BASE_URL)
const val can only be used with primitive types and String.
Java interoperability
From Java, companion object members are normally accessed through Companion:
class MathUtils {
companion object {
fun double(x: Int): Int = x * 2
}
}
Java usage:
int result = MathUtils.Companion.double(5);
If you want Java callers to use it like a real static method, add @JvmStatic:
class MathUtils {
companion object {
@JvmStatic
fun double(x: Int): Int = x * 2
}
}
Then Java can call:
int result = MathUtils.double(5);
For constants:
class Constants {
companion object {
const val APP_NAME = "MyApp"
}
}
Java can access this as:
String appName = Constants.APP_NAME;
Companion objects can implement interfaces
Because companion objects are real objects, they can implement interfaces:
interface Parser<T> {
fun parse(value: String): T
}
class User(val name: String) {
companion object : Parser<User> {
override fun parse(value: String): User {
return User(value)
}
}
}
Usage:
fun main() {
val user = User.parse("Alice")
println(user.name)
}
You can also pass the companion object where the interface is expected:
fun <T> parseWith(parser: Parser<T>, value: String): T {
return parser.parse(value)
}
val user = parseWith(User, "Bob")
Here, User refers to the companion object when used as a value.
Key points
- Use
companion objectfor static-like members. - Access members as
ClassName.member. - Use
const valfor compile-time constants. - Use
@JvmStaticif Java callers need static-style access. - Companion objects are real singleton objects.
- Companion objects can have names and implement interfaces.
