JvmOverloads - An essential annotation for Kotlin/Java interrop

Adam McNeilly - Jun 22 '17 - - Dev Community

When I first began writing Kotlin, one of my favorite benefits was default arguments. This is a way for you to create a function (or constructor) that would accept default values for something. This way, the user of that method would only have to give you what you need. However, that ability to overload functions with default values doesn't interrop 1-1 to Java, and if you are going to be using both languages in your app, you'll need to be sure to include the @JvmOverloads annotation. This post explains the interrop problem and how that annotation solves it.

Overloaded Constructors In Java

Before showing how they work in Kotlin, here is how you would typically write overloaded constructors in Java:

public class Task {
    private String description;
    private Date date;
    private boolean isCompleted;

    public Task() {
        this("");
    }

    public Task(String description) {
        this(description, new Date());
    }

    public Task(String description, Date date) {
        this(description, date, false);
    }

    public Task(String description, Date date, boolean isCompleted) {
        this.description = description;
        this.date = date;
        this.isCompleted = isCompleted;
    }
}
Enter fullscreen mode Exit fullscreen mode

Default Parameters In Kotlin

Here is how the class would be defined in Kotlin, or so I thought:

// In Task.kt
data class Task(var description: String? = "", var date: Date? = Date(), var isCompleted: Boolean = false)

// In MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    // All of these statements are valid
    val task1 = Task()
    val task2 = Task("Test")
    val task3 = Task("Test", Date())
    val task4 = Task("Test", Date(), false)
}
Enter fullscreen mode Exit fullscreen mode

It won't take long to realize how much time and code we saved by using default arguments, without losing any of that flexibility. Or did we?

The Java Interrop Snag

After writing the above Kotlin class with the default arguments, I wanted to use the same idea in Java, but unfortunately it didn't work:

Task task1 = new Task(); // Valid - Java allows default constructor
Task task2 = new Task("Test"); // Compiler error - no constructor 
Task task3 = new Task("Test", new Date()); // Compiler error - no constructor
Task task4 = new Task("Test", new Date(), false); // Valid
Enter fullscreen mode Exit fullscreen mode

I was immediately confused. It worked in Kotlin but not Java, and I knew that there was 100% interrop, so I began looking into the difference. It was surprisingly simple - Kotlin, having its own constructor, can handle rules such as default arguments however it wants - separate from Java. However, if we do want full Java interrop, we need to create all of the necessary overloads.

JvmOverloads Annotation

Thankfully, the solution is just a simple annotation. @JvmOverloads tells the Kotlin compiler to generate an overload for the constructor or function for however many default arguments are supplied. Adding it, our data class would look like this:

data class Task @JvmOverloads constructor(var description: String? = "", var date: Date? = Date(), var isCompleted: Boolean = false)
Enter fullscreen mode Exit fullscreen mode

Now, any time we use this class in Java we can use whichever overloaded version of the constructor we deem necessary, and still only need a single line of Kotlin code to define the object.

. . . . . . . . . . . . . . . . . . . . . . . .