Question
Kotlin Companion Object Explained: Static Replacement, Meaning, and Best Practices
Question
In Kotlin, I have mostly used a companion object as a replacement for Java static members whenever I need class-level data or behavior.
I want to understand the intended meaning and proper use of companion object more clearly:
- Why is it called a companion object?
- If I need multiple static-like properties or functions, do they all need to be grouped inside a single
companion objectblock? - If I want a singleton instance associated with a class, I sometimes write code like this:
class MyService {
companion object {
val singleton by lazy { MyService() }
}
}
This works, but it feels a little unidiomatic. What is the better Kotlin way to handle this?
Short Answer
By the end of this page, you will understand what a companion object is in Kotlin, why it exists, how it differs from Java static, and when it is the right tool. You will also learn idiomatic ways to define constants, factory methods, and singleton-style objects in Kotlin.
Concept
Kotlin does not have static members in the same way Java does. Instead, Kotlin uses objects for shared, single-instance behavior.
A companion object is a special object declared inside a class that is tied to that class definition. It acts like the class's built-in companion: a singleton object that lives alongside the class rather than inside each instance.
Why Kotlin uses this design
In Java, static means “belongs to the class, not to any object instance.” Kotlin takes a more object-oriented approach:
- shared behavior is still represented by an object
- that object can have properties, functions, interfaces, and inheritance
- the object can be named or left as the default
Companion
This gives Kotlin more flexibility than a plain static keyword.
Why it is called "companion"
It is called a companion object because it is an object that accompanies a class. It is closely associated with the class and can access that class's private members.
Example:
class User private constructor(val name: String) {
companion object {
fun : User {
User(name)
}
}
}
Mental Model
Think of a class as a blueprint for objects.
Each time you create an instance, you build one house from the blueprint.
A companion object is like a small office attached to the blueprint itself:
- it is not one of the houses
- it exists only once
- it stores information and tools related to the blueprint
So:
- instance properties belong to each house
- companion object members belong to the office attached to the blueprint
A plain object declaration is different: that is just one building that already exists, not a blueprint plus an attached office.
Syntax and Examples
Basic syntax
class Example {
companion object {
const val VERSION = "1.0"
fun greet() {
println("Hello from companion object")
}
}
}
Use it like this:
fun main() {
println(Example.VERSION)
Example.greet()
}
Named companion object
A companion object can have a name:
class MathHelper {
companion object Factory {
fun double(x: Int) = x * 2
}
}
You can call it in either of these ways:
println(MathHelper.double(5))
println(MathHelper.Factory.double(5))
Companion object for factory methods
Step by Step Execution
Consider this example:
class Counter {
companion object {
var created = 0
}
init {
created++
}
}
fun main() {
println(Counter.created)
val a = Counter()
val b = Counter()
println(Counter.created)
}
Step-by-step
1. The class is defined
Counter has:
- a companion object with a property
created - an
initblock that runs every time aCounterinstance is created
2. println(Counter.created) runs
At this point, no Counter instances exist yet.
Output:
0
3. val a = Counter() runs
Real World Use Cases
Common practical uses
Constants related to a class
class HttpClient {
companion object {
const val DEFAULT_TIMEOUT_MS = 5000
}
}
Useful when a constant is strongly tied to the meaning of a class.
Factory methods
class Order private constructor(val id: String) {
companion object {
fun create(id: String): Order = Order(id.trim())
}
}
Useful when creating an object requires validation or formatting.
Parsing and conversion helpers
class Color(val red: Int, val green: Int, val blue: Int) {
companion object {
: Color {
clean = hex.removePrefix()
Color(
clean.substring(, ).toInt(),
clean.substring(, ).toInt(),
clean.substring(, ).toInt()
)
}
}
}
Real Codebase Usage
In real projects, developers usually use companion objects in a few predictable ways.
1. Named constructors and factory methods
Instead of exposing many constructors, a class may provide clear creation methods:
class Token private constructor(val value: String) {
companion object {
fun fromHeader(header: String): Token {
return Token(header.removePrefix("Bearer "))
}
}
}
This improves readability and validation.
2. Constants and configuration defaults
class CacheManager {
companion object {
const val MAX_ITEMS = 100
}
}
This keeps class-specific constants close to the class.
3. Validation before object creation
class Email private constructor(val value: String) {
{
: Email {
require( value) { }
Email(value)
}
}
}
Common Mistakes
1. Using a companion object when a plain object is better
Less ideal
class AppLogger {
companion object {
fun log(message: String) {
println(message)
}
}
}
If AppLogger will never have normal instances, this is simpler:
object AppLogger {
fun log(message: String) {
println(message)
}
}
2. Treating companion objects as exactly the same as Java static
They are similar in usage, but not identical in meaning.
A companion object is an actual object, so it can:
- have its own type
- implement interfaces
- be passed around as a value
3. Forcing unrelated values into a class companion
Broken design example:
class User {
{
= text
=
= amount *
}
}
Comparisons
| Concept | What it represents | Best use case | Example |
|---|---|---|---|
companion object | One singleton object attached to a class | Class-level functions, constants, factories | User.create() |
object declaration | One standalone singleton | Global service or manager with exactly one instance | object Logger |
| instance members | Data and behavior per object | State unique to each created object | user.name |
| top-level function/property | File-level declaration | Utilities or values not tied to one class | fun parsePort() |
Cheat Sheet
Core idea
A companion object is a singleton object attached to a class.
Syntax
class Example {
companion object {
const val MAX = 10
fun create(): Example = Example()
}
}
Access
Example.MAX
Example.create()
Named companion
class Example {
companion object Factory
}
Rules
- A class can have only one companion object.
- A companion object is a real object.
- It can contain properties, functions, and interface implementations.
- It is commonly used for constants, factories, and class-level helpers.
Use object instead when
- you need exactly one instance in the whole program
- you do not need separate class instances
FAQ
Why does Kotlin use companion objects instead of static?
Kotlin models class-level behavior as objects. This is more flexible because the companion object is a real object that can hold state, implement interfaces, and be passed around.
Why is it called a companion object?
Because it is an object that stays alongside a class and is closely associated with it.
Can a Kotlin class have more than one companion object?
No. A class can have only one companion object.
Should I use a companion object for singletons?
Only if the singleton is specifically a shared instance related to a class that can also have normal instances. If you need exactly one instance overall, prefer an object declaration.
Is companion object the same as Java static?
Not exactly. It often fills the same role, but it is implemented as a real singleton object rather than a language-level static member.
Can companion objects access private constructors?
Yes. This is why they are often used for factory methods.
When should I use top-level functions instead of a companion object?
Use top-level declarations when the function or property is not strongly tied to a specific class.
Is val singleton by lazy { ... } inside a companion object wrong?
No. It is valid and sometimes useful. But if the whole type should only ever have one instance, object is usually simpler and more idiomatic.
Mini Project
Description
Build a small Kotlin class that represents an application configuration. The class should support normal instances, but also provide class-level helpers using a companion object. This demonstrates when a companion object is useful for constants, factory methods, and a shared default instance.
Goal
Create a class with instance data plus a companion object that exposes a constant, a factory method, and a shared default configuration.
Requirements
- Create a
AppConfigclass with at least two instance properties. - Add a companion object with one constant.
- Add a factory method that builds an
AppConfigfrom an environment name such asdevorprod. - Add a shared default instance in the companion object.
- Show usage of both custom instances and the shared instance.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.