Question

Java Static Methods in Kotlin: Equivalents and Best Practices

kotlinjavastaticstatic-methods

Question

In Java, methods can be declared with the static keyword, but Kotlin does not have a static keyword.

What is the best way to represent a Java static method in Kotlin, and which Kotlin feature should be used in different situations?

Short Answer

By the end of this page, you will understand how Kotlin handles functionality that would be written as static in Java. You will learn when to use top-level functions, companion objects, and object declarations, and how each option maps to common Java patterns.

Concept

Kotlin does not use the static keyword. Instead, it offers several language features that cover the same use cases more cleanly.

In Java, static is often used for:

  • utility methods
  • factory methods
  • constants
  • shared state
  • methods tied to a class rather than an instance

In Kotlin, the equivalent depends on why the method is static.

1. Top-level functions

A function can be declared directly in a file, outside any class:

fun add(a: Int, b: Int): Int {
    return a + b
}

This is the most common replacement for Java utility-style static methods. If the function does not need access to class internals, a top-level function is often the simplest and most idiomatic choice.

2. Companion objects

If the function conceptually belongs to a class, put it inside a companion object:

class User(val name: String) {
    companion object {
        fun createGuest(): User {
            return User("Guest")
        }
    }
}

This is commonly used for factory methods and class-level behavior.

3. Object declarations

If you want a singleton container for related functions or state, use object:

object MathUtils {
    fun square(x: Int): Int {
        return x * x
    }
}

This is useful when you want namespacing or a single shared instance.

Why this matters

Choosing the right Kotlin feature makes code:

  • easier to read
  • more idiomatic
  • easier to test
  • better organized

A direct one-to-one translation from Java static is usually not the goal. The goal is to choose the Kotlin feature that best expresses the intent.

Mental Model

Think of Java static as saying: "this belongs to the class itself, not to a particular object."

Kotlin splits that idea into different tools:

  • Top-level function = a tool placed directly on the workbench, ready to use
  • Companion object = a tool stored inside a specific class's cabinet
  • Object declaration = a special toolbox that exists only once

So instead of one static keyword doing many jobs, Kotlin gives you separate features for separate purposes.

That makes your intent clearer:

  • Is this just a general utility? Use a top-level function.
  • Is this part of how a class is created or managed? Use a companion object.
  • Do you want one shared holder for behavior or state? Use an object declaration.
Take Quiz

Syntax and Examples

Top-level function

fun greet(name: String): String {
    return "Hello, $name"
}

fun main() {
    println(greet("Ava"))
}

Use this when the function does not logically belong to a class.

Companion object

class Logger private constructor(val prefix: String) {
    companion object {
        fun default(): Logger {
            return Logger("APP")
        }
    }
}

fun main() {
    val logger = Logger.default()
    println(logger.prefix)
}

Use this when the method is related to the class itself, such as a factory method.

Object declaration

object StringUtils {
    fun isBlankOrShort(text: String): Boolean {
        return text.isBlank() || text.length < 3
    }
}

fun main() {
    println(StringUtils.isBlankOrShort("Hi"))
}

Use this when you want a singleton namespace for related methods.

Java interoperability with @JvmStatic

If Java code needs to call a Kotlin method like a true static-style method, you can use @JvmStatic inside an object or companion object:

class Config {
    companion object {
        @JvmStatic
        fun load(): String {
            return "loaded"
        }
    }
}

This is especially useful when Kotlin code must work nicely with existing Java codebases.

Step by Step Execution

Consider this example:

class User(val name: String) {
    companion object {
        fun guest(): User {
            return User("Guest")
        }
    }
}

fun main() {
    val user = User.guest()
    println(user.name)
}

Step by step:

  1. class User(val name: String) defines a class with one property: name.
  2. Inside the class, companion object defines class-level behavior.
  3. fun guest(): User creates and returns a new User object.
  4. In main(), User.guest() is called.
  5. Kotlin resolves that call through the companion object.
  6. The function returns User("Guest").
  7. The returned object is stored in user.
  8. println(user.name) prints Guest.

Although this looks similar to a Java static factory method, Kotlin is using a companion object instead of a static keyword.

Real World Use Cases

Utility functions

Top-level functions are commonly used for:

  • formatting dates
  • parsing strings
  • validating input
  • converting data

Example:

fun normalizeEmail(email: String): String {
    return email.trim().lowercase()
}

Factory methods

Companion objects are useful when creating objects in controlled ways:

class ApiClient private constructor(val baseUrl: String) {
    companion object {
        fun production(): ApiClient {
            return ApiClient("https://api.example.com")
        }
    }
}

Shared configuration or helpers

object declarations are useful for one shared holder:

object AppConfig {
    const val API_VERSION = "v1"
}

Java interoperability

When a Kotlin library is used from Java, @JvmStatic helps expose methods in a familiar static style.

Take Quiz

Real Codebase Usage

In real Kotlin projects, developers usually choose based on ownership and intent.

Common patterns

Top-level utility functions

Used for pure operations that do not depend on object state.

fun parsePort(value: String): Int {
    return value.toIntOrNull() ?: 8080
}

This is common in:

  • validation helpers
  • mapping functions
  • formatting code
  • extension-support utilities

Companion object for factories

Used when object creation should be controlled or named.

class Session private constructor(val token: String) {
    companion object {
        fun fromToken(token: String): Session {
            require(token.isNotBlank())
            return Session(token)
        }
    }
}

This often appears with:

  • validation before construction
  • named constructors
  • parsing methods such as fromJson()
  • singleton access points

Object declaration for shared services

Used when there should be exactly one instance.

object IdGenerator {
    private var nextId = 1

    fun generate(): Int {
        return nextId++
    }
}

Guard clauses and validation

Companion object methods often validate arguments before building an instance.

Mapping and conversion

Top-level functions are often used to convert DTOs, API responses, or database rows into domain models.

Error handling

Factory methods may return a valid object only when input passes checks, sometimes using require, check, or nullable returns.

Common Mistakes

1. Translating every Java static method into a companion object

Beginners often put everything into a companion object just because it feels closest to Java.

class MathUtils {
    companion object {
        fun add(a: Int, b: Int): Int = a + b
    }
}

This works, but if the function is just a general helper, a top-level function is usually cleaner:

fun add(a: Int, b: Int): Int = a + b

2. Using an object when no singleton is needed

If you only want a simple helper function, an object may add unnecessary structure.

Broken design:

object EmailHelper {
    fun normalize(email: String): String = email.trim().lowercase()
}

Often simpler:

fun normalizeEmail(email: String): String = email.trim().lowercase()

3. Forgetting Java interoperability needs

If Java code must call the method like a static method, you may need @JvmStatic.

class Example {
    companion object {
        @JvmStatic
        fun hello() {
            println("Hello")
        }
    }
}

4. Storing mutable shared state carelessly

Shared state inside object or companion object can create bugs.

object Counter {
    var count = 0
}

This global state can be changed from anywhere, so use it carefully.

5. Confusing class-level behavior with instance behavior

If a method needs object data, it should not be treated like static behavior.

Broken example:

class Person(val name: String) {
    companion object {
        fun printName() {
            // Cannot access name here
        }
    }
}

The companion object does not have access to instance properties unless an instance is passed in.

Comparisons

Kotlin featureBest use caseSimilar to Java static?Notes
Top-level functionGeneral utility functionsYes, oftenMost idiomatic for standalone helpers
companion objectClass-related methodsYesGood for factory methods and class-level logic
object declarationSingleton containerSometimesUseful for shared state or grouped behavior
@JvmStaticJava-friendly accessYesMainly for interoperability with Java

Top-level function vs companion object

ChoiceUse whenExample
Top-level functionThe function does not belong to a classfun formatName(name: String)
Companion objectThe function is part of class creation or class semanticsUser.fromJson(text)

Companion object vs object declaration

ChoiceUse whenExample
Companion objectBehavior belongs to one specific classOrder.create()
Object declarationBehavior is grouped in a standalone singletonAppLogger.log()
Take Quiz

Cheat Sheet

Quick rules

  • Kotlin has no static keyword.
  • Use a top-level function for standalone helpers.
  • Use a companion object for class-related methods.
  • Use an object declaration for a singleton.
  • Use @JvmStatic when Java interoperability requires static-style access.

Syntax

Top-level function

fun doWork() {
    println("working")
}

Companion object

class MyClass {
    companion object {
        fun create(): MyClass = MyClass()
    }
}

Object declaration

object Utils {
    fun help() {}
}

Java-friendly static access

class Example {
    companion object {
        @JvmStatic
        fun run() {}
    }
}

Best practice shortcuts

  • Utility helper? Top-level function
  • Named constructor or factory? Companion object
  • One shared instance? Object declaration
  • Called heavily from Java? Consider @JvmStatic

Edge cases

  • Companion objects are objects, not true language-level statics.
  • Top-level functions compile in a way that Java can call, but the Java syntax differs.
  • Avoid unnecessary global mutable state in objects or companion objects.

FAQ

What is the Kotlin equivalent of a Java static method?

Usually a top-level function or a method inside a companion object, depending on whether the method belongs to a class.

Does Kotlin support static methods?

Not with a static keyword. Kotlin uses other features to cover the same use cases.

When should I use a companion object in Kotlin?

Use it when the function is logically tied to a class, such as factory methods, constants, or class-level utilities.

Are top-level functions better than companion objects?

For standalone utility behavior, yes. They are often simpler and more idiomatic.

What is @JvmStatic in Kotlin?

@JvmStatic tells Kotlin to expose a method in a Java-friendly static style when used inside a companion object or object.

Can a companion object access instance properties?

No. A companion object does not automatically have access to instance data. It can only use class-level members or work with an instance passed to it.

Should I use object for utility methods?

Sometimes, but only when a singleton container makes sense. For many helpers, top-level functions are simpler.

How do I convert Java utility classes to Kotlin?

In many cases, convert their static methods into top-level functions. If grouping is useful, consider an object.

Take Quiz

Mini Project

Description

Build a small Kotlin program that models a Temperature class and demonstrates three ways to replace Java-style static methods: a top-level conversion function, a companion object factory method, and an object-based utility helper. This project helps you practice choosing the right Kotlin feature for the right kind of class-level behavior.

Goal

Create and use top-level, companion object, and object declaration methods in one Kotlin program.

Requirements

  • Create a top-level function that converts Celsius to Fahrenheit.
  • Create a Temperature class with a companion object factory method.
  • Create an object utility that checks whether a temperature is freezing.
  • In main, call all three and print the results.
  • Keep the code valid and idiomatic Kotlin.
Take Quiz

Keep learning

Related questions

View more