Kotlin features almost excellent compatibility with Java. It’s not surprising since it was built on top of the JVM and a lot of its syntax sugar is actually implemented by using standard Java features!
For example, if we define an extension method on a standard String class, underneath it’s just a standard static method:
fun String.asHelloWorld() : String = "Hello $this" // public static final java.lang.String asHelloWorld(java.lang.String)
So… I wonder how Kotlin’s internal classes are represented in and seen from pure Java then?
Let’s see.
Introducing Kotlin’s “internal” Modifier
For those that do not know, internal modifier restricts the visibility of an element to a particular module:
internal class Foo
In the above example, the Foo class will only be accessible from a particular Maven module (or equivalents for other build tools).
Kotlin’s “internal” Modifier as Seen From Java
This is where things become… complicated. There’s no equivalent of the internal modifier in Java language, so what happens if we compile an internal class with kotlinc and try to use it from Java?
Let’s do that and see:
// javap com.pivovarit.hello.Foo Compiled from "Hello.kt" public final class com.pivovarit.hello.Foo { public com.pivovarit.hello.Foo(); }
As you can see, Kotlin’s workaround for the sake of achieving interoperability with Java… is to treat internal classes as public ones.
This can be surprising and undesired, but understandable at the same time.
However, let’s see what happens if we enrich the class with some members:
internal class Foo { var name: String = "John" }
Compiled from "Hello.kt" public final class com.pivovarit.hello.Foo { public com.pivovarit.hello.Foo(); public final java.lang.String getName(); public final void setName(java.lang.String); }
Members seem unaffected as well. They are all public. However, the magic happens if members become internal as well:
internal class Foo { internal var name: String = "John" }
// javap com.pivovarit.hello.Foo Compiled from "Hello.kt" public final class com.pivovarit.hello.Foo { public com.pivovarit.hello.Foo(); public final java.lang.String getName$kotlin_internal(); public final void setName$kotlin_internal(java.lang.String); }
As you can see, members are still public but their names are no longer the same! They were mangled so that they are harder to call by accident from Java!
But what happens if we make members internal while leaving the class public?
class Foo { internal var name: String = "John" }
// javap com.pivovarit.hello.Foo Compiled from "Hello.kt" public final class com.pivovarit.hello.Foo { public com.pivovarit.hello.Foo(); public final java.lang.String getName$kotlin_collections(); public final void setName$kotlin_collections(java.lang.String); }
Looks like it worked as well which means that it doesn’t matter if we make the class internal or not!
Conclusion
Interoperability is a difficult subject if you’re trying to mix two languages with features that can be directly translated(transpiled?) from one to the other.
This all can be confusing, but it’s crucial to understand if we’re about to mix Kotlin and Java. Especially when calling Kotlin code from Java and not the other way around.