Question
In Kotlin, how can you define a static extension method? Is that possible?
For example, I currently have this extension function:
fun Uber.doMagic(context: Context) {
// ...
}
This can be called on an instance:
val uberInstance = Uber()
uberInstance.doMagic(context)
But how can I make it work like this instead?
Uber.doMagic(context)
In other words, can Kotlin extension functions be defined as static or class-level methods for a type rather than instance-level calls?
Short Answer
By the end of this page, you will understand how Kotlin extension functions really work, why Uber.doMagic() is not the same as uberInstance.doMagic(), and what alternatives Kotlin provides when you want class-level or utility-style behavior. You will also see practical patterns using companion object, regular functions, and companion extensions.
Concept
Kotlin extension functions let you add callable behavior to an existing type without modifying its source code.
For example:
fun Uber.doMagic(context: Context) {
// ...
}
This means: “add a function named doMagic that can be called as if it belongs to an Uber instance.”
Important idea
Extension functions do not actually change the class. They are just regular functions that the compiler lets you call with dot syntax.
So this:
uberInstance.doMagic(context)
is essentially compiled like a normal function call where uberInstance is passed as an argument.
Why Uber.doMagic(context) does not work
Uber by itself refers to the class, not an instance of the class. Your extension is defined on Uber instances, so Kotlin expects something like:
val uber = Uber()
uber.doMagic(context)
Mental Model
Think of an extension function like a shortcut label attached outside a box.
- The box is the original class, such as
Uber - The shortcut label says, “when you have an
Uberobject, you may call this extra function” - But the label is not stored inside the class itself
So if you have an actual box:
val uber = Uber()
uber.doMagic(context)
that works.
But Uber as a class name is more like the blueprint for boxes, not a box itself. An instance extension works on boxes, not on blueprints.
If you want behavior on the blueprint-like name, use a companion object or a top-level function.
Syntax and Examples
1. Instance extension function
This is what you already have:
class Uber
fun Uber.doMagic(context: Context) {
println("Magic on an Uber instance")
}
Usage:
val uber = Uber()
uber.doMagic(context)
This works because the extension receiver is an instance of Uber.
2. Top-level function as a utility
If you do not need an instance, use a normal function:
fun doMagic(context: Context) {
println("Magic without an Uber instance")
}
Usage:
doMagic(context)
This is the simplest option when no object state is needed.
3. Use a companion object
If you want class-like access such as Uber.doMagic(...), define a companion object:
Step by Step Execution
Consider this example:
class Uber {
companion object
}
fun Uber.doMagic() {
println("instance extension")
}
fun Uber.Companion.doMagic() {
println("companion extension")
}
fun main() {
val uber = Uber()
uber.doMagic()
Uber.doMagic()
}
What happens step by step
1. val uber = Uber()
This creates an instance of the Uber class.
2. uber.doMagic()
Kotlin looks for a callable function for an Uber instance.
It finds:
fun Uber.doMagic()
So it calls the instance extension and prints:
Real World Use Cases
Instance extension functions
Useful when behavior depends on a specific object.
Examples:
- Formatting a model object for display
- Converting an entity to a DTO
- Adding helper behavior to Android
Context,View, orFragment - Validation logic that depends on object data
Example:
fun User.fullName(): String = "$firstName $lastName"
Companion object functions
Useful when behavior belongs to the type as a whole.
Examples:
- Factory methods such as
User.fromJson(...) - Validation rules that create objects
- Parsing helpers
- Constants and type-level utility methods
Example:
class User(val name: String) {
companion object {
fun fromName(name: ): User = User(name.trim())
}
}
Real Codebase Usage
In real Kotlin codebases, developers usually pick one of these patterns:
1. Top-level helper functions
Very common for stateless logic.
fun parseUberId(value: String): Int = value.toInt()
Why teams use it:
- simple
- easy to test
- no unnecessary class wrapping
2. Extension functions for readability
Often used to make code read naturally.
fun String.isValidUberCode(): Boolean = length == 8
Why teams use it:
- expressive call sites
- good for transformations and small helpers
3. Companion objects for factory methods
A common pattern when object creation should be controlled.
class Uber private constructor(val id: Int) {
companion object {
: Uber {
id = idText.toIntOrNull() ?: error()
Uber(id)
}
}
}
Common Mistakes
1. Expecting extension functions to truly become class members
Beginners often think this:
fun Uber.doMagic() {}
adds a real method inside Uber.
It does not. It only adds a new callable syntax for code that can see the extension.
2. Trying to call an instance extension on the class name
Broken example:
class Uber
fun Uber.doMagic() {}
fun main() {
Uber.doMagic() // Error
}
Why it fails:
Uberis a type name here- the extension expects an
Uberinstance
Fix:
val uber = Uber()
uber.doMagic()
or use a companion-based solution.
3. Confusing companion object functions with Java static
Comparisons
| Approach | Call syntax | Needs instance? | Best for |
|---|---|---|---|
| Instance extension | uber.doMagic() | Yes | Behavior related to one object |
| Top-level function | doMagic() | No | General utility logic |
| Companion object member | Uber.doMagic() | No | Type-level behavior, factories |
| Companion object extension | Uber.doMagic() | No | Add class-like behavior from outside the class |
Instance extension vs companion extension
class {
}
= println()
= println()
Cheat Sheet
// Instance extension
fun Uber.doMagic(context: Context) { }
Uber().doMagic(context)
// Top-level utility function
fun doMagic(context: Context) { }
doMagic(context)
// Companion object member
class Uber {
companion object {
fun doMagic(context: Context) { }
}
}
Uber.doMagic(context)
// Companion object extension
class Uber {
companion object
}
fun Uber.Companion.doMagic(context: Context) { }
Uber.doMagic(context)
Rules to remember
fun TypeName.function()extends instances of that typeTypeName.function()only works iffunctionbelongs to the companion object or a companion extension- Kotlin extensions do not modify the original class
FAQ
Can Kotlin extension functions be static?
Not in the Java static sense. An extension on Uber applies to Uber instances. For class-level syntax, use a companion object or an extension on the companion object.
How do I call a function like Uber.doMagic() in Kotlin?
Define doMagic inside companion object, or define an extension on Uber.Companion.
What is the closest thing to a static extension method in Kotlin?
An extension function on a companion object:
fun Uber.Companion.doMagic() { }
Do extension functions actually modify the class?
No. They are resolved by the compiler and behave like regular functions with special call syntax.
Should I use a companion object or a top-level function?
Use a companion object when the behavior logically belongs to the type. Use a top-level function when it is just a general helper.
Can I extend a class companion object from another file?
Yes, as long as the class has a companion object.
Why does fun Uber.doMagic() not allow ?
Mini Project
Description
Create a small Kotlin example that demonstrates the difference between an instance extension function and a companion object extension function. This project is useful because many Kotlin developers need to choose between object-level behavior and type-level behavior when designing APIs.
Goal
Build a class where one function is called on an instance and another is called on the class name using a companion object extension.
Requirements
- Create a class named
Uber. - Add an instance extension function that prints a message.
- Add a companion object extension function that prints a different message.
- Call both functions from
main. - Make the output clearly show which function was used.
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.