1. Overview

One way in which the Kotlin language differs from Java is that Kotlin doesn’t contain the static keyword that we’re familiar with.

In this quick tutorial, we’ll see a few ways to achieve Java’s static method behavior in Kotlin.

2. Package-Level Functions

Let’s start by creating a LoggingUtils.kt file. Here, we’ll create a very simple method called debug. Since we don’t care much about the functionality inside our method, we’ll just print a simple message.

Since we are defining our method outside of a class, it represents a package-level function:

fun debug(debugMessage : String) {
    println("[DEBUG] $debugMessage")
}

We’ll also see in the decompiled code that our debug method is now declared as static:

public final class LoggingUtilsKt {
    public static final void debug(@NotNull String debugMessage) {
        Intrinsics.checkParameterIsNotNull(debugMessage, "debugMessage");
        String var1 = "[DEBUG] " + debugMessage;
        System.out.println(var1);
    }
}

3. Companion Objects

Kotlin allows us to create objects that are common to all instances of a class – the companion objects. We can create a singleton instance of an object just by adding the keyword companion.

Let’s define our debug method inside a companion object:

class ConsoleUtils {
    companion object {
        fun debug(debugMessage : String) {
            println("[DEBUG] $debugMessage")
        }
    }
}

Our decompiled code shows us that we can access the debug method via the Companion object:

public final class ConsoleUtils {
    public static final ConsoleUtils.Companion Companion
      = new ConsoleUtils.Companion((DefaultConstructorMarker) null);

    public static final class Companion {
        public final void debug(@NotNull String debugMessage) {
            Intrinsics.checkParameterIsNotNull(debugMessage, "debugMessage");
            String var2 = "[DEBUG] " + debugMessage;
            System.out.println(var2);
        }

        private Companion() {}

        public Companion(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}

To avoid calling the resulting instance by the generic name Companion, we can also give a custom name.

Finally, to make the debug method static again, we should use the @JvmStatic annotation:

class ConsoleUtils {
    companion object {
        @JvmStatic
        fun debug(debugMessage : String) {
            println("[DEBUG] $debugMessage")
        }
    }
}

By using it, we’ll end up with an actual static final void debug method in our decompiled code:

public final class ConsoleUtils {
    public static final ConsoleUtils.Companion Companion
      = new ConsoleUtils.Companion((DefaultConstructorMarker) null);

    @JvmStatic
    public static final void debug(@NotNull String debugMessage) {
        Companion.debug(debugMessage);
    }

    public static final class Companion {
        @JvmStatic
        public final void debug(@NotNull String debugMessage) {
            Intrinsics.checkParameterIsNotNull(debugMessage, "debugMessage");
            String var2 = "[DEBUG] " + debugMessage;
            System.out.println(var2);
        }

        private Companion() {}

        public Companion(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}

Now, we’ll be able to access this new method directly via the ConsoleUtils class.

4. Conclusion

In this short tutorial, we saw how to replicate in Kotlin the behavior of Java static methods. We’ve used package-level functions and also companion objects.

The implementation of all of these snippets can be found over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.