Calling Kotlin from Java
Kotlin code can be easily called from Java. For example, instances of a Kotlin class can be seamlessly created and operated in Java methods. However, there are certain differences between Java and Kotlin that require attention when integrating Kotlin code into Java. On this page, we'll describe the ways to tailor the interop of your Kotlin code with its Java clients.
Properties
A Kotlin property is compiled to the following Java elements:
a getter method, with the name calculated by prepending the
get
prefix.a setter method, with the name calculated by prepending the
set
prefix (only forvar
properties).a private field, with the same name as the property name (only for properties with backing fields).
For example, var firstName: String
compiles to the following Java declarations:
If the name of the property starts with is
, a different name mapping rule is used: the name of the getter is the same as the property name, and the name of the setter is obtained by replacing is
with set
. For example, for a property isOpen
, the getter is called isOpen()
and the setter is called setOpen()
. This rule applies for properties of any type, not just Boolean
.
Package-level functions
All the functions and properties declared in a file app.kt
inside a package org.example
, including extension functions, are compiled into static methods of a Java class named org.example.AppKt
.
To set a custom name to the generated Java class, use the @JvmName
annotation:
Having multiple files with the same generated Java class name (the same package and the same name or the same @JvmName
annotation) is normally an error. However, the compiler can generate a single Java facade class which has the specified name and contains all the declarations from all the files which have that name. To enable the generation of such a facade, use the @JvmMultifileClass
annotation in all such files.
Instance fields
If you need to expose a Kotlin property as a field in Java, annotate it with the @JvmField
annotation. The field has the same visibility as the underlying property. You can annotate a property with @JvmField
if it:
has a backing field
is not private
does not have
open
,override
orconst
modifiersis not a delegated property
Late-Initialized properties are also exposed as fields. The visibility of the field is the same as the visibility of the lateinit
property setter.
Static fields
Kotlin properties declared in a named object or a companion object have static backing fields either in that named object or in the class containing the companion object.
Usually these fields are private, but they can be exposed in one of the following ways:
@JvmField
annotationlateinit
modifierconst
modifier
Annotating such a property with @JvmField
makes it a static field with the same visibility as the property itself.
A late-initialized property in an object or a companion object has a static backing field with the same visibility as the property setter.
Properties declared as const
(in classes as well as at the top level) are turned into static fields in Java:
In Java:
Static methods
As mentioned above, Kotlin represents package-level functions as static methods. Kotlin can also generate static methods for functions defined in named objects or companion objects if you annotate those functions as @JvmStatic
. If you use this annotation, the compiler generates both a static method in the enclosing class of the object and an instance method in the object itself. For example:
Now, callStatic()
is static in Java while callNonStatic()
is not:
Similarly, for named objects:
In Java:
Starting from Kotlin 1.3, @JvmStatic
applies to functions defined in companion objects of interfaces as well. Such functions compile to static methods in interfaces. Note that static method in interfaces were introduced in Java 1.8, so be sure to use the corresponding targets.
You can also apply @JvmStatic
annotation to the property of an object or a companion object making its getter and setter methods static members in that object or the class containing the companion object.
Default methods in interfaces
When targeting the JVM, Kotlin compiles functions declared in interfaces to default methods unless configured otherwise. These are concrete methods in interfaces that Java classes can inherit directly, without reimplementation.
Here is an example of a Kotlin interface with a default method:
The default implementation is available for Java classes implementing the interface.
Implementations of the interface can override default methods.
Compatibility modes for default methods
Kotlin provides three modes for controlling how functions in interfaces are compiled to JVM default methods. These modes determine whether the compiler generates compatibility bridges and static methods in DefaultImpls
classes.
You can control this behavior using the -jvm-default
compiler option:
Learn more about compatibility modes:
enable
Default behavior. Generates default implementations in interfaces and includes compatibility bridges and DefaultImpls
classes. This mode maintains compatibility with older compiled Kotlin code.
no-compatibility
Generates only default implementations in interfaces. Skips compatibility bridges and DefaultImpls
classes. Use this mode for new codebases that don't interact with code that relies on DefaultImpls
classes. This can break binary compatibility with older Kotlin code.
disable
Disables default implementations in interfaces. Only compatibility bridges and DefaultImpls
classes are generated.
Visibility
The Kotlin visibility modifiers map to Java in the following way:
private
members are compiled toprivate
members.private
top-level declarations are compiled toprivate
top-level declarations. Package-private accessors are also included, if accessed from within a class.protected
remainsprotected
. (Note that Java allows accessing protected members from other classes in the same package and Kotlin doesn't, so Java classes will have broader access to the code.)internal
declarations becomepublic
in Java. Members ofinternal
classes go through name mangling, to make. it harder to accidentally use them from Java and to allow overloading for members with the same signature that don't see each other according to Kotlin rules.public
remainspublic
.
KClass
Sometimes you need to call a Kotlin method with a parameter of type KClass
. There is no automatic conversion from Class
to KClass
, so you have to do it manually by invoking the equivalent of the Class<T>.kotlin
extension property:
Handling signature clashes with @JvmName
Sometimes we have a named function in Kotlin, for which we need a different JVM name in bytecode. The most prominent example happens due to type erasure:
These two functions can not be defined side-by-side, because their JVM signatures are the same: filterValid(Ljava/util/List;)Ljava/util/List;
. If we really want them to have the same name in Kotlin, we can annotate one (or both) of them with @JvmName
and specify a different name as an argument:
From Kotlin, they are accessible by the same name filterValid
, but from Java it is filterValid
and filterValidInt
.
The same trick applies when we need to have a property x
along with a function getX()
:
To change the names of generated accessor methods for properties without explicitly implemented getters and setters, you can use @get:JvmName
and @set:JvmName
:
Overloads generation
Normally, if you write a Kotlin function with default parameter values, it is visible in Java only as a full signature, with all parameters present. If you wish to expose multiple overloads to Java callers, you can use the @JvmOverloads
annotation.
The annotation also works for constructors, static methods, and so on. It can't be used on abstract methods, including methods defined in interfaces.
For every parameter with a default value, this generates one additional overload, which has this parameter and all parameters to the right of it in the parameter list removed. In this example, the following is generated:
Note that, as described in Secondary constructors, if a class has default values for all constructor parameters, a public constructor with no arguments is generated for it. This works even if the @JvmOverloads
annotation is not specified.
Checked exceptions
Kotlin does not have checked exceptions. So, normally the Java signatures of Kotlin functions do not declare exceptions thrown. Thus, if you have a function in Kotlin like this:
And you want to call it from Java and catch the exception:
You get an error message from the Java compiler, because writeToFile()
does not declare IOException
. To work around this problem, use the @Throws
annotation in Kotlin:
Null-safety
When calling Kotlin functions from Java, nobody prevents us from passing null
as a non-nullable parameter. That's why Kotlin generates runtime checks for all public functions that expect non-nulls. This way we get a NullPointerException
in the Java code immediately.
Variant generics
When Kotlin classes make use of declaration-site variance, there are two options of how their usages are seen from the Java code. For example, imagine you have the following class and two functions that use it:
A naive way of translating these functions into Java would be this:
The problem is that in Kotlin you can write unboxBase(boxDerived(Derived()))
but in Java that would be impossible because in Java the class Box
is invariant in its parameter T
, and thus Box<Derived>
is not a subtype of Box<Base>
. To make this work in Java, you would have to define unboxBase
as follows:
This declaration uses Java's wildcards types (? extends Base
) to emulate declaration-site variance through use-site variance, because it is all Java has.
To make Kotlin APIs work in Java, the compiler generates Box<Super>
as Box<? extends Super>
for covariantly defined Box
(or Foo<? super Bar>
for contravariantly defined Foo
) when it appears as a parameter. When it's a return value, wildcards are not generated, because otherwise Java clients will have to deal with them (and it's against the common Java coding style). Therefore, the functions from our example are actually translated as follows:
If you need wildcards where they are not generated by default, use the @JvmWildcard
annotation:
In the opposite case, if you don't need wildcards where they are generated, use @JvmSuppressWildcards
:
Translation of type Nothing
The type Nothing
is special, because it has no natural counterpart in Java. Indeed, every Java reference type, including java.lang.Void
, accepts null
as a value, and Nothing
doesn't accept even that. So, this type cannot be accurately represented in the Java world. This is why Kotlin generates a raw type where an argument of type Nothing
is used:
Inline value classes
If you want Java code to work smoothly with Kotlin's inline value classes, you can use the @JvmExposeBoxed
annotation or the -Xjvm-expose-boxed
compiler option. These approaches ensure Kotlin generates the necessary boxed representations for Java interoperability.
By default, Kotlin compiles inline value classes to use unboxed representations, which are often inaccessible from Java. For example, you can't call the constructor for the MyInt
class from Java:
So the following Java code fails:
You can use the @JvmExposeBoxed
annotation so that Kotlin generates a public constructor that you can call from Java directly. You can apply the annotation at the following levels to ensure fine-grained control over what's exposed to Java:
Class
Constructor
Function
Before using the @JvmExposeBoxed
annotation in your code, you must opt in by using @OptIn(ExperimentalStdlibApi::class)
. For example:
With these annotations, Kotlin generates a Java-accessible constructor for the MyInt
class and a variant for the extension function that uses the boxed form of the value class. So the following Java code runs successfully:
To apply this behavior to all inline value classes and the functions that use them within a module, compile it with the -Xjvm-expose-boxed
option. Compiling with this option has the same effect as if every declaration in the module has the @JvmExposeBoxed
annotation.