Static Extension Methods in Kotlin: Best Practices 2025

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Static Extension Methods in Kotlin: Myth or Reality?

\n\n

Imagine this scenario: you're writing in Kotlin and want to add an isValidEmail() method for the String class. Easy — you create an extension function. But what if you need this method to be static, like String.isValidEmail(email), rather than email.isValidEmail()? That's where things get interesting.

\n\n

The question about static extension methods in Kotlin is one of the most popular on Stack Overflow (over 100,000 views). And it's no surprise: developers coming from C# or Java are accustomed to static utility methods. Kotlin, however, offers a more flexible but not always obvious approach. In this article, we'll cover all the ways to create a static extension in Kotlin, best practices for 2025, and common mistakes.

\n\n

Why Are Static Extension Methods Difficult?

\n\n

In Kotlin, extension functions are not static methods in the classical sense by default. They compile as regular static functions but are called as instance methods. This works perfectly for most tasks, but sometimes static semantics are needed.

\n\n

Consider three scenarios where this is required:

\n
    \n
  • Factory methods — creating objects with validation (e.g., User.fromJson())
  • \n
  • Utilities with context — methods that only need the class, not an instance
  • \n
  • Integration with Java code — when Java colleagues expect static calls
  • \n
\n\n

Method #1: Companion object + extension (2025 Classic)

\n\n

The most popular and recommended way is to add an extension function to the class's companion object. This works like a static method, but with one important caveat: the companion object must exist.

\n\n
class StringUtils {\n    companion object // mandatory!\n}\n\nfun StringUtils.Companion.isValidEmail(email: String): Boolean {\n    return email.matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))\n}\n\n// Usage:\nStringUtils.isValidEmail("test@example.com") // true\n
\n\n

Pros: clean API, intuitive for Java developers.

\n

Cons: requires creating a wrapper class. If the companion object is absent — the extension won't work.

\n\n

Method #2: Top-level functions with naming (Kotlin-way)

\n\n

In Kotlin, it's common to use top-level functions in a separate file. These aren't exactly static extension methods, but they behave identically.

\n\n
// File: StringExtensions.kt\npackage com.pythonlib.utils\n\nfun String.isValidEmail(): Boolean {\n    return this.matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))\n}\n\n// Usage:\n"test@example.com".isValidEmail() // true\n
\n\n

If you specifically need a static call (like StringUtils.isValidEmail(email)), use @JvmStatic with a companion object:

\n\n
class StringUtils {\n    companion object {\n        @JvmStatic\n        fun isValidEmail(email: String): Boolean {\n            return email.matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))\n        }\n    }\n}\n
\n\n

Important: @JvmStatic generates a real static method for Java compatibility. Without it, from Java you would have to call StringUtils.Companion.isValidEmail().

\n\n

Method #3: @JvmStatic annotation + extension (advanced)

\n\n

You can combine: make an extension to the companion object and add @JvmStatic. This provides both a static call from Java and convenient syntax from Kotlin.

\n\n
class DateTimeUtils {\n    companion object\n}\n\nfun DateTimeUtils.Companion.nowFormatted(): String {\n    return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)\n}\n\n// Kotlin:\nDateTimeUtils.nowFormatted()\n\n// Java (with @JvmStatic):\nDateTimeUtils.nowFor                

Blogs

Book Recommendations