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)
If you want something callable on the class-like name itself, Kotlin does not have Java-style static members. Instead, Kotlin uses:
- top-level functions
- companion objects
- extension functions on companion objects
What matters in real programming
This distinction matters because developers often want one of two different things:
- behavior tied to a specific object instance
- utility behavior tied to a type or namespace
Choosing the right approach keeps your code easier to read and maintain.
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:
class Uber {
companion object {
fun doMagic(context: Context) {
println("Magic from Uber companion")
}
}
}
Usage:
Uber.doMagic(context)
4. Extension function on a companion object
You can also extend the companion object itself:
class Uber {
companion object
}
fun Uber.Companion.doMagic(context: Context) {
println("Magic from companion extension")
}
Usage:
Uber.doMagic(context)
This is the closest match to a “static extension method” in Kotlin.
When to use which
- Use
fun Uber.doMagic()when the function needs an Uber instance - Use
fun doMagic()when the function is just a utility - Use
companion objectwhen the function belongs to the type itself - Use
fun Uber.Companion.doMagic()when you want extension-style syntax on the companion
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:
instance extension
3. Uber.doMagic()
Here, Uber refers to the companion object access point, not an instance.
Kotlin looks for a function that works with Uber.Companion.
It finds:
fun Uber.Companion.doMagic()
So it prints:
companion extension
Key takeaway
The two calls look similar, but they target different receivers:
uber.doMagic()→ receiver is anUberinstanceUber.doMagic()→ receiver isUber.Companion
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: String): User = User(name.trim())
}
}
Companion object extensions
Useful when:
- you cannot edit the original class directly
- the class already has a companion object
- you want
ClassName.function()syntax from outside the class
Example:
class Config {
companion object
}
fun Config.Companion.loadDefault(): Config {
return Config()
}
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 {
fun create(idText: String): Uber {
val id = idText.toIntOrNull() ?: error("Invalid id")
return Uber(id)
}
}
}
4. Guard clauses and validation
Companion or top-level functions are often used for validation before object creation.
fun requireValidName(name: String) {
require(name.isNotBlank()) { "Name must not be blank" }
}
5. Extension on companion for external libraries
If a class comes from a library and exposes a companion object, teams may extend it externally.
class ExternalType {
companion object
}
fun ExternalType.Companion.defaultInstance(): ExternalType = ExternalType()
This gives convenient call syntax without modifying library code.
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
Kotlin does not use static the same way Java does.
Instead of:
Uber.doMagic();
in Kotlin you typically write a companion object function:
class Uber {
companion object {
fun doMagic() {}
}
}
4. Using an extension when a normal function is clearer
Not everything should be an extension.
Less clear:
fun Uber.Companion.calculateTax(amount: Double): Double = amount * 0.2
Sometimes clearer:
fun calculateTax(amount: Double): Double = amount * 0.2
Use the style that best matches the meaning.
5. Forgetting the companion object declaration
This will not work if there is no companion object to extend:
class Uber
fun Uber.Companion.doMagic() {} // Error if no companion exists
Fix:
class Uber {
companion object
}
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 Uber {
companion object
}
fun Uber.doMagic() = println("instance")
fun Uber.Companion.doMagic() = println("companion")
Usage:
Uber().doMagic() // instance
Uber.doMagic() // companion
Companion member vs companion extension
Companion member
Defined inside the class:
class Uber {
companion object {
fun doMagic() {}
}
}
Companion extension
Defined outside the class:
class Uber {
companion object
}
fun Uber.Companion.doMagic() {}
Use a companion member when you control the class source. Use a companion extension when you want to add behavior from outside.
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
- Kotlin has no direct
statickeyword for normal class members like Java - If no instance is needed, a top-level function may be the simplest choice
Quick decision guide
- Need object data? → use an instance extension
- Need class-level access? → use a companion object
- Cannot edit the class but need
ClassName.function()? → use a companion extension - Just a helper? → use a top-level function
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 Uber.doMagic()?
Because the receiver is an Uber instance, not the class name. Uber.doMagic() requires a companion-based receiver instead.
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.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
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.