Authors Top

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

1. Overview

In this article, we’ll talk about the absence of checked exceptions and its implications in Kotlin. Along the way, we’re going to learn how the @Throws annotation facilitates calling Kotlin methods and functions from Java. We’ll also learn when we should and when we shouldn’t use this annotation in our projects.

2. No Checked Exceptions

In Kotlin, there’s no such thing as checked exceptions, as opposed to Java. So, we don’t have to declare Java checked exceptions using a throws clause in Kotlin. As a matter of fact, Kotlin doesn’t even have a throws clause at all:

fun throwJavaUnchecked() {
    throw IllegalArgumentException()
}

fun throwJavaChecked() {
    throw IOException()
}

In the above examples, we’re throwing a checked and an unchecked exception from the Java standard library. As is obvious from this example, Kotlin doesn’t care about the type of exception in the Java world and treats all of them the same.

Since there are no checked exceptions in Kotlin, we can easily throw them in different functions without even declaring them in the method signatures.

3. Java Interoperability

As we all know, one of the most compelling features of Kotlin is its great interoperability with Java. Now, given the fact that Kotlin doesn’t have checked exceptions, let’s call the above functions from a Java class:

public class Caller {

    public static void main(String[] args) {
        unchecked();
    }

    public static void unchecked() {
        try {
            ThrowsKt.throwJavaUnchecked();
        } catch (IllegalArgumentException e) {
            System.out.println("Caught something!");
        }
    }
}

Here, we’re calling the throwJavaUnchecked() function (which is defined in the throws.kt file, hence the ThrowsKt name) from Java and catching the IllegalArgumentException exception. Since this exception is an unchecked one in Java, the code compiles and runs perfectly fine. Therefore, if we compile and run it, it’ll catch the exception and print the expected output.

On the other hand, if we try to catch the checked IOException when calling the throwJavaChecked() function, the code won’t even compile:

public static void checked() {
    try {
        ThrowsKt.throwJavaChecked();
    } catch (IOException e) {
        System.out.println("Won't even compile");
    }
}

Here, the Java compiler will fail with the error message:

java: exception java.io.IOException is never thrown in body of corresponding try statement

The error message is sort of obvious: it tells us that the called function (in this case, throwJavaChecked) didn’t declare the IOException exception, but the caller method is catching it. In Java, we can’t catch checked exceptions without declaring them using a throws clause.

Now, let’s see Kotlin’s answer for this interoperability issue.

4. The @Throws Annotation

To fix this Java interoperability issue, Kotlin provides the @Throws annotation. If we annotate a Kotlin method or function with @Throws, Kotlin will compile that method or function with a throws clause in its signature:

@Throws(IOException::class)
fun throwJavaChecked() {
    throw IOException()
}

As shown above, we’re declaring that the throwJavaChecked() function will throw an IOException. Obviously, this information is only beneficial for Java clients and provides no value whatsoever for pure Kotlin codebases.

Now in Java clients, when calling this function, we should either catch the IOException:

try {
    ThrowsKt.throwJavaChecked();
} catch (IOException e) {
    System.out.println("It works this time!");
}

or declare it in the throws clause:

public static void checked() throws IOException {
    ThrowsKt.throwJavaChecked();
}

So, putting this annotation above a Kotlin function is somehow similar to defining a Java method with a throws clause. Please note that, as of Kotlin 1.4, there is a typealias for the @Throws annotation:

@SinceKotlin("1.4")
public actual typealias Throws = kotlin.jvm.Throws

Therefore, if we’re using an older version, we should import the annotation appropriately:

import kotlin.jvm.Throws

@Throws(IOException::class)
fun throwJavaChecked() {
    throw IOException()
}

4.1. Bytecode Representation

Now that we know how to use the annotation, let’s see what it looks like under the hood. First, we should compile the Kotlin file using kotlinc:

>> kotlinc throws.kt

Then, we can use the javap tool to take a peek at the generated bytecode:

>> javap -c -p com.baeldung.throwsannotation.ThrowsKt
// omitted
public static final void throwJavaChecked() throws java.io.IOException;

As shown above, the Kotlin compiler emits a throws clause in the bytecode. This is the effect of using the @Throws annotation. For the comparison, the bytecode for the other function is something like:

public static final void throwJavaUnchecked();

Obviously, there is no sign of a throws clause here.

5. Conclusion

In this article, we learned that there is no such thing as checked exceptions in Kotlin. Moreover, we used the @Throws annotation to facilitate using checked exceptions when calling Kotlin functions from a Java client.

By now, we should also know that there’s no point in using this annotation for pure Kotlin projects as its sole purpose is to provide better Java interoperability.

As usual, all the examples are available over on GitHub.

Authors Bottom

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

Comments are closed on this article!