Some things in a program should exist exactly once: the app's settings, a logger, a connection to a database, a registry of available payment methods. Creating several copies would waste memory or, worse, let them disagree. A class that has exactly one instance is called a singleton.
Kotlin makes singletons trivial: replace class with object, and Kotlin creates the single instance for you the first time it's used. There's no need for the private constructors and static fields used in Java.
Kotlin also has no static keyword. Instead, a class can contain a companion object, which holds members that belong to the class itself rather than to each object, such as constants, counters and factory functions like User.fromJson(...). In this lesson you'll use both, and meet object expressions for creating one-off objects on the spot.
Object declarations (singletons)
object Settings {
var darkMode = false
val version = "2.4.1"
fun summary() = "v$version, dark mode: $darkMode"
}
Settings.darkMode = true
println(Settings.summary())- Use the object's name directly; there's no constructor and no
Settings()call. - The instance is created lazily the first time it's accessed, and it's thread-safe.
- Objects can implement interfaces and extend classes, like any class.
- Every part of the program sees the same instance, so changes are shared.
Companion objects
A companion object lives inside a class. Its members are called on the class name, like static members in other languages:
class User private constructor(val name: String) {
companion object {
const val MAX_NAME = 20
fun create(name: String): User? =
if (name.isBlank() || name.length > MAX_NAME) null else User(name.trim())
}
}
val u = User.create("Ada")const valconstants in a companion are a common home for class-related constants.- A private constructor plus a companion function creates a factory: the only way to make a
Useris throughcreate, which validates the input. - There's one companion per class, shared by all its objects.
Object expressions
An object expression creates a one-off object right where you need it, usually to implement an interface without writing a named class:
val logger = object : Logger {
override fun log(msg: String) = println("[app] $msg")
}This is useful for small listeners and test doubles. When the interface has a single function, a lambda is often shorter still, using a fun interface.
Data objects and top-level functions
A data object (Kotlin 1.9 and later) is a singleton with a readable toString(), which is handy in sealed hierarchies such as data object Loading : State().
Not everything needs an object. Kotlin allows top-level functions and properties, declared in a file outside any class. For a general utility like fun formatPrice(...), a top-level function is simpler than wrapping it in an object. Use objects when you need shared state, an interface implementation or a clear grouping.
Companion objects that implement interfaces
A companion object is a real object, so it can implement an interface. That lets you pass the class name itself wherever the interface is expected, which is a tidy way to attach a parser or factory to a type:
interface Parser<T> {
fun parse(text: String): T
}
data class Colour(val r: Int, val g: Int, val b: Int) {
companion object : Parser<Colour> {
override fun parse(text: String): Colour {
val (r, g, b) = text.split(",").map { it.trim().toInt() }
return Colour(r, g, b)
}
}
}
fun <T> readAll(lines: List<String>, parser: Parser<T>) = lines.map(parser::parse)
println(Colour.parse("255, 0, 10")) // Colour(r=255, g=0, b=10)
println(readAll(listOf("1,2,3", "4,5,6"), Colour))In the last line, Colour on its own refers to the companion object, so it can be handed to readAll like any other Parser. A companion can also be given a name, as in companion object Factory, but most code leaves it unnamed and calls members through the class name.
const val or plain val?
Both declare a value that never changes, but they're not the same:
const val | val | |
|---|---|---|
| Where | Top level, or inside an object or companion | Anywhere |
| Allowed types | Primitive types such as Int and Double, and String | Any type |
| Value worked out | When compiling, and copied into the code that uses it | When the object or file is first used |
So const val MAX_NAME = 20 is fine, but const val START = LocalDate.of(2024, 1, 1) fails with "Only primitive types and 'String' are allowed". Use a plain val for anything built by calling a function.
When an object exists only to implement a single-function interface, a fun interface with a lambda is shorter still: val logger = Logger { println("[app] $it") } creates the implementation in one line.
Kotlin Objects Syntax
Object forms
object Registry { val items = mutableListOf<String>() }
Registry.items.add("x")
class Temp private constructor(val c: Double) {
companion object {
const val ABSOLUTE_ZERO = -273.15
fun of(c: Double) = if (c < ABSOLUTE_ZERO) null else Temp(c)
}
}
Temp.of(20.0)
val listener = object : Clickable { override fun click() { } }Kotlin Objects Summary
object Name { }declares a singleton, created on first use and shared everywhere.- A companion object holds class-level members, called with the class name.
- Private constructors plus companion functions make validating factory functions.
object : Interface { }creates a one-off anonymous object.- Prefer top-level functions for simple utilities, and keep shared mutable state small.
Kotlin Objects Examples
Each example shows the code, the output it produces and what each part does.
A singleton counter
object VisitCounter {
private var count = 0
fun visit(page: String): Int {
count++
println("Visited $page")
return count
}
val total: Int get() = count
}
fun main() {
VisitCounter.visit("home")
VisitCounter.visit("shop")
VisitCounter.visit("cart")
println("Total visits: ${VisitCounter.total}")
}Visited home
Visited shop
Visited cart
Total visits: 3Line-by-line explanation
- Line 1object declares the class and its single instance at once.
- Line 2count is private, so only the object's functions can change it.
- Lines 12 to 14Every call uses the same instance, so the count keeps growing.
A factory in a companion object
class Temperature private constructor(val celsius: Double) {
val fahrenheit get() = celsius * 9 / 5 + 32
companion object {
const val ABSOLUTE_ZERO = -273.15
fun fromCelsius(c: Double): Temperature? =
if (c < ABSOLUTE_ZERO) null else Temperature(c)
fun fromFahrenheit(f: Double): Temperature? = fromCelsius((f - 32) * 5 / 9)
}
}
fun main() {
val t = Temperature.fromCelsius(25.0)
println(t?.fahrenheit)
println(Temperature.fromFahrenheit(212.0)?.celsius)
println(Temperature.fromCelsius(-300.0))
println(Temperature.ABSOLUTE_ZERO)
}77.0
100.0
null
-273.15Line-by-line explanation
- Line 1The private constructor means code outside can't call Temperature(...) directly.
- Lines 7 to 8The factory validates first, returning null for impossible temperatures.
- Line 10A second factory converts from Fahrenheit, then reuses the first one.
- Line 19Companion members are reached through the class name.
An object expression
interface Greeter {
fun greet(name: String): String
}
fun welcomeAll(names: List<String>, greeter: Greeter) {
names.forEach { println(greeter.greet(it)) }
}
fun main() {
val formal = object : Greeter {
override fun greet(name: String) = "Good morning, $name."
}
val friendly = object : Greeter {
override fun greet(name: String) = "Hey $name!"
}
welcomeAll(listOf("Ada", "Linus"), formal)
welcomeAll(listOf("Sam"), friendly)
}Good morning, Ada.
Good morning, Linus.
Hey Sam!Line-by-line explanation
- Lines 10 to 12An anonymous object implements Greeter without a named class.
- Lines 16 to 17The same function behaves differently depending on the greeter passed in.
Common Kotlin Objects Mistakes
Calling a constructor on an object
val s = Settings() // error: Settings is an object, not a classSettings.darkMode = trueLooking for static
class MathUtils {
static fun square(n: Int) = n * n // not Kotlin
}fun square(n: Int) = n * n // a top-level functionGlobal mutable state everywhere
object Cart { var items = mutableListOf<String>() } // any code can replace or change itclass Cart { private val items = mutableListOf<String>() /* add(), remove(), total() */ }Pass a Cart to the code that needs it, so changes are easy to follow and test.
Kotlin Objects in Real Projects
Objects in Kotlin projects
Android apps use objects for app-wide constants and simple registries, and companion objects for factory functions such as Fragment.newInstance(...). Libraries use companion factories to hide complex construction behind a clear name, as in Json { prettyPrint = true } and LocalDate.of(...). Dependency injection frameworks like Hilt and Koin manage singletons for you in larger apps.
Kotlin Objects FAQ
Does Kotlin have static methods?
Not as a keyword. Top-level functions cover most uses, and members of a companion object are called on the class name, as in User.create("Ada"), which feels like a static call. If Java code needs a real static method, add the @JvmStatic annotation to the companion member.
When is a Kotlin object created?
An object declaration is created the first time your code uses it, not when the program starts, and the JVM makes sure that happens only once, even with several threads. A companion object is set up when its containing class is first loaded. Object expressions, by contrast, create a new object every time the expression runs.
What is the difference between object and data object?
Both are singletons. A data object also gets a readable toString() that prints just its name, such as Loading, plus matching equals and hashCode. A plain object prints something like Loading@4e25154f. Use data object for states in a sealed hierarchy, where you'll often print or log them.
Can a Kotlin object have a constructor?
No. An object declaration can't take constructor parameters, because there's only one instance and Kotlin creates it for you. If you need to pass in values, use a class instead, or give the object an initialisation function, though shared state set up that way is easy to get wrong.
Is a Kotlin object the same as a singleton in Java?
It does the same job as the classic Java singleton pattern, with a private constructor and a single static instance, but without the boilerplate. Kotlin handles lazy, thread-safe creation for you. From Java, you reach it through a static field called INSTANCE, as in Settings.INSTANCE.
Kotlin Objects Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the declaration so Config is a singleton.
Config {
val apiUrl = "https://api.example.com"
}
How do you call create in a companion object of class User?
What does this print?
object Counter { var n = 0 }
Counter.n++
Counter.n += 5
println(Counter.n)
Why combine a private constructor with a companion factory function?
When is a top-level function better than an object holding the function?
Kotlin Objects Quiz
5 questions to check what you learned.
What is a singleton?
- A class with exactly one instance
- A class with one property
- A function with one parameter
- A list with one item
Does Kotlin have a static keyword?
- Yes
- Only for functions
- No; companion objects and top-level declarations replace it
- Only in interfaces
When is an object declaration's instance created?
- When the program starts
- The first time it's accessed
- Every time it's used
- Never
What does
object : Greeter { ... }create?- A new interface
- A singleton named Greeter
- An anonymous object that implements Greeter
- A companion object
Why is shared mutable state in singletons risky?
- It uses more memory
- Objects can't hold var properties
- It's slower
- Any code can change it, making bugs hard to trace and tests interfere with each other