Static Methods in Kotlin: How a Java Developer Can Navigate the World of object and companion
\n\nIf you write in Java and are just starting to learn Kotlin, you've probably asked yourself: "Where is static here?". In Java, we are used to writing public static void main() or MathUtils.max() — it's convenient, simple, and doesn't require creating an object. But in Kotlin, the keyword static does not exist. At all. Completely.
Don't panic. Kotlin developers haven't gone crazy — they simply decided that static methods can be implemented more elegantly, without global state and with a clearer architecture. In this article, we'll cover all the ways to replace Java static in Kotlin: from simple package-level functions to the powerful companion object. You'll learn when to use what and see live code examples.
Let's go! 🚀
\n\n\n\n
Why is there no static in Kotlin? (and that's a good thing)
\n\nIn Java, static is essentially a global variable or method tied to the class, not to an instance. It's convenient for utilities, but violates OOP principles: static members cannot be overridden, are difficult to test, and create tight couplings.
Kotlin encourages a cleaner approach: functions and properties can exist at the package level (without a class at all!) or be organized into object singletons. And if you need to bind a method to a class (like in Java), use companion object. Let's break down each option.
\n\n
Method 1: Package-Level Functions (Simplest)
\n\nIn Kotlin, you can declare a function or property simply in a file, outside a class. This is an analog of a static method in Java, but without the unnecessary wrapper of a utility class.
\n\nExample:
\n\n// File: MathUtils.kt\npackage com.example.utils\n\nfun max(a: Int, b: Int): Int {\n return if (a > b) a else b\n}\n\nval PI = 3.14159 // package-level constant\n\nNow this function can be called from anywhere (after import):
\n\nimport com.example.utils.max\nimport com.example.utils.PI\n\nfun main() {\n println(max(10, 20)) // 20\n println(PI) // 3.14159\n}\n\nWhen to use: for simple utility functions and constants that don't require grouping into a class. This is the easiest way to get "static" behavior.
\n\n\n\n
Method 2: Object Singleton (for a Group of Related Functions)
\n\nIf you need to group several static methods together (like MathUtils in Java), use an object. This is a singleton — a single instance that is created automatically on first access.
Example:
\n\nobject StringUtils {\n fun isEmpty(str: String?): Boolean {\n return str.isNullOrEmpty()\n }\n\n fun capitalizeFirst(str: String): String {\n return str.replaceFirstChar { it.uppercase() }\n }\n\n const val EMPTY_STRING = ""\n}\n\nUsage:
\n\nfun main() {\n println(StringUtils.isEmpty(null)) // true\n println(StringUtils.capitalizeFirst("kotlin")) // Kotlin\n println(StringUtils.EMPTY_STRING) // ""\n}\n\nImportant: object is a full-fledged class. It can implement interfaces, have initializers, and even inherit from other classes. But it cannot have constructors (except private).
\n\n
Method 3: Companion Object (for Methods Related to a Class)
\n\nThis is the closest analog to Java static inside a class. companion object is a companion object that lives inside the class. Its methods and properties are called via the class name, like static ones.
Example:
\n\nclass User(val name: String, val age: Int) {\n companion