Question
Java Static Method Equivalents in Kotlin: Companion Objects and Top-Level Functions
Question
Kotlin does not have a static keyword. What is the best way to represent a static Java method in Kotlin, and when should you use a top-level function, a companion object, or @JvmStatic?
Short Answer
Kotlin has no static members because it uses other language features to model shared behavior. You will learn the main alternatives—top-level functions, companion object functions, and singleton object declarations—and how to expose Kotlin APIs conveniently to Java when needed.
Concept
Java uses static to attach a method or field to a class rather than to an individual object.
public class TextUtils {
public static boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}
Kotlin separates the different reasons developers use static methods:
- Top-level functions are functions declared directly in a Kotlin file. They are usually the best choice for general utility behavior that does not belong to a particular class.
- A companion object holds behavior logically associated with a class, such as factory methods, constants, and parsing methods.
- An
objectdeclaration creates a named singleton. It is useful when shared state or a named service is genuinely needed. @JvmStaticis primarily for Java interoperability. It changes how Java callers access a function; Kotlin callers generally do not need it.
This matters because Kotlin code is designed around clear ownership. Instead of automatically putting every utility into a class merely to obtain static, Kotlin lets a function exist without a containing class.
Mental Model
Think of a Java class with static methods as a noticeboard mounted on a building: everyone uses the same board, and nobody needs to enter the building first.
Kotlin offers several places to put that noticeboard:
- A top-level function is a public noticeboard in the file itself.
- A companion object is a noticeboard attached to one specific class.
- An
objectdeclaration is one shared office with its own noticeboard and its own stored information.
Choose the location based on who owns the behavior. A text-formatting helper may belong at the top level. A User factory belongs beside User. A shared application configuration service may need a singleton object.
Syntax and Examples
1. Top-level function
Declare a function outside any class:
// TextUtils.kt
fun isBlank(value: String?): Boolean {
return value == null || value.isBlank()
}
fun main() {
println(isBlank(" ")) // true
}
This is often the most idiomatic Kotlin replacement for a Java utility class containing only static methods. Kotlin code calls it directly as isBlank(...).
On the JVM, Kotlin compiles this function into a generated class based on the file name. From Java, the default call is TextUtilsKt.isBlank(value).
2. Companion object
Use a companion object when the function conceptually belongs to a class:
class User private constructor(val name: String) {
companion object {
fun create(name: String): User {
require(name.isNotBlank()) { }
User(name.trim())
}
}
}
{
user = User.create()
println(user.name)
}
Step by Step Execution
Consider a class factory implemented with a companion object:
class Temperature private constructor(val celsius: Double) {
companion object {
fun fromFahrenheit(fahrenheit: Double): Temperature {
val celsius = (fahrenheit - 32) * 5 / 9
return Temperature(celsius)
}
}
}
fun main() {
val temperature = Temperature.fromFahrenheit(68.0)
println(temperature.celsius)
}
Step by step:
Temperaturehas a private constructor, so code outside the class cannot writeTemperature(20.0).- The
companion objectprovides a class-related creation function namedfromFahrenheit. maincallsTemperature.fromFahrenheit(68.0)without first creating aTemperatureinstance.
Real World Use Cases
- Factory methods: Create objects from alternative input formats, such as
Money.fromCents(499)orLocalDate.parse("2025-03-08"). - Validation helpers: Use top-level functions such as
isValidEmail(value)when they are not owned by one domain class. - Constants related to a type: Put values such as a maximum allowed length in a companion object.
- Parsing and serialization: Provide
Order.fromJson(json)in a companion object when parsing creates anOrder. - Shared services: Use an
objectfor small, stateless coordinators or for a deliberately shared component such as an application logger wrapper. - Java-compatible libraries: Add
@JvmStaticto selected companion functions when Java consumers should callType.method()rather thanType.Companion.method().
Real Codebase Usage
A useful decision rule is to start with the least stateful option.
Utility behavior: top-level functions
For behavior that does not need class state and is not strongly owned by a type, use a top-level function:
fun requireNonEmpty(value: String, fieldName: String): String {
require(value.isNotBlank()) { "$fieldName must not be blank" }
return value.trim()
}
Top-level functions avoid Java-style StringUtils or ValidationUtils classes that exist only to hold static methods.
Creation and parsing: companion objects
Use a companion object for factories, parsers, and type-level configuration:
class ApiKey private constructor(val value: String) {
companion object {
fun parse(raw: String): ApiKey {
require(raw.startsWith("key_")) { "Invalid API key" }
return ApiKey(raw)
}
}
}
Common Mistakes
Treating object as the default replacement for every static method
This works, but can create an unnecessary singleton:
object MathHelpers {
fun square(number: Int) = number * number
}
If the function has no state and does not belong to a type, a top-level function is simpler:
fun square(number: Int) = number * number
Adding @JvmStatic when Kotlin is the only caller
class User {
companion object {
@JvmStatic
fun guest() = User()
}
}
This is not wrong, but it is unnecessary for Kotlin callers. Use @JvmStatic only when Java call syntax or a Java framework specifically benefits from it.
Assuming a companion member is truly a Java static member
Without @JvmStatic, Java must access the companion object:
Comparisons
| Kotlin approach | Best for | Kotlin call | Java call | Shared mutable state? |
|---|---|---|---|---|
| Top-level function | General utilities with no clear owning type | formatName(name) | FileNameKt.formatName(name) by default | No |
companion object function | Factories, parsing, and behavior owned by a class | User.create(name) | User.Companion.create(name) by default | Possible, but usually avoid it |
Companion function with @JvmStatic | Companion APIs used from Java | User.create(name) |
Cheat Sheet
-
Kotlin has no
statickeyword. -
Use a top-level function for a general, stateless utility.
fun slugify(text: String): String = text.lowercase().replace(" ", "-") -
Use a companion object for behavior owned by a class.
class User private constructor() { companion object { fun guest(): User = User() } } -
Call companion functions with the class name:
val user = User.guest() -
Use a standalone
objectfor one shared instance:object Counter { var value = 0 }
FAQ
Does Kotlin have static methods?
No. Kotlin does not use the static keyword. Top-level functions, companion objects, and object declarations cover the common use cases.
What is the most idiomatic Kotlin replacement for a Java static utility method?
Usually a top-level function, especially when the function has no state and does not naturally belong to a class.
When should I use a companion object in Kotlin?
Use it for operations tied to a type, such as fromJson, parse, create, of, and constants related to that class.
Is a Kotlin companion object the same as Java static?
Not exactly. It is an object associated with a class. Kotlin lets you call its members as ClassName.member(), but Java normally sees it as ClassName.Companion.member() unless @JvmStatic is used.
When do I need @JvmStatic?
Use it when Java code, a Java-based framework, or a Java-facing API needs conventional static access to a companion member. Kotlin callers do not need it.
Can an object contain properties and methods?
Yes. An object is a singleton instance, so it can contain functions and properties, including mutable properties. Use mutable global state carefully.
Mini Project
Description
Build a small ProductCode value type. Product codes must be normalized and validated before an instance is created. The project demonstrates why a companion object is a good replacement for a Java static factory method: creation rules stay next to the type they protect.
Goal
Create validated ProductCode objects with ProductCode.from(rawCode) and add a top-level function for formatting a display label.
Requirements
Create a ProductCode class whose constructor cannot be called directly.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.