Kotlin equivalence of Java try-with-resource statement

dimitrilc 2 Tallied Votes 342 Views Share

Introduction

If you are a Java developer coming to Kotlin, you might have wondered how to use a language construct that is similar to the try-with-resource statement in Java to automatically close Autocloseable/Closeable resources for you.

Luckily, Kotlin provides the inline extension function use() that provides similar functionality to the try-with-resource block.

In this tutorial, we will learn how to use the use() extension function with Autocloseable resources.

Goals

At the end of the tutorial, you would have learned:

  1. How to use the use() extension function.
  2. How it differs from the Java try-with-resource statement.

Prerequisite Knowledge

  1. Basic Kotlin.
  2. Basic Java IO/NIO concepts.

Tools Required

  1. A Kotlin IDE such as IntelliJ Community Edition.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Kotlin project, using JDK 16 as the project JDK and Gradle 7.2 as the build tool.

  2. Copy and paste the configuration code below into your build.gradle.kts file.

     import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
    
     plugins {
        kotlin("jvm") version "1.5.31"
     }
    
     group = "com.example"
     version = "1.0-SNAPSHOT"
    
     repositories {
        mavenCentral()
     }
    
     dependencies {
        testImplementation(kotlin("test"))
     }
    
     tasks.test {
        useJUnitPlatform()
     }
    
     tasks.withType<KotlinCompile>() {
        kotlinOptions.jvmTarget = "11"
     }
  3. Under src/main/kotlin, create a new package called com.example.

  4. Under src/main/kotlin/com/example, create a new Kotlin file called Entry.kt.

  5. Create the main() function inside this file.

  6. Under src/main, create a new source set to contain Java code.

  7. Under src/main/java, create a new package called com.example.

  8. Under src/main/java/com/example, Create a new empty class called JavaTest.

Java try-with-resource Overview

To provide some context, we will start with reviewing the try-with-resource statement in a very simplistic way.

Java has a language construct called try-with-resource statement to automatically call the close() method on resources declared in its declaration statement. The resources that are declared must implement Autocloseable/Closeable or there will be a compiler error.

In the JavaTest.java class, copy and paste the code snippet below:

    package com.example;

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;

    public class JavaTest {
       public void tryWithResource() {
           try(var reader = new BufferedReader(new FileReader(""))){ //1
               //do nothing
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
    }

The method tryWithResource() in the code snippet is only meant to show usage of the try-with-resource statement syntax. On line 1, we declared the BufferedReader resource, which implements the Closeable interface. We did not have to call close() manually because the try-with-resource statement will automatically do that for us.

Kotlin use() function

Kotlin actually does not have a language construct like try-with-resource. The recommended way to automatically close resources is to use the inline extension function use(). Its function signature is:

inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R

Firstly, let us deconstruct the function signature so we can understand how to use this function:

  1. inline: indicates that this function is an inline function.
  2. T : AutoCloseable?: this part of the generics means T must implement AutoCloseable and a T reference is nullable.
  3. R: this is the return type of the lambda block.
  4. T.use: T is the receiver type here. This means that this use() function can be called from any T(Autocloseable) reference.
  5. block: (T) -> R: the function type. The lambda expression that implements this function type will receive the Closeable resource T to operate on.

In simple terms, we can call the use() function from any AutoCloseable reference. We must pass an implementation of the function type (T) -> R as an argument. The most popular way to do that would be to use the passing-trailing-lambda syntax.

Now that we have understood the anatomy of the use() function, it is time to write some code.

In the Entry.kt file, create a Test class that implements Autocloseable using the code snippet below.

    class Test: AutoCloseable { //1

       fun doThrow(): Nothing = throw RuntimeException("${hashCode()} throwing from doThrow()") //2

       override fun close() = throw IOException("${hashCode()} throwing from close()") //3

    }

The Autocloseable interface only requires us to implement one abstract method, which is

public void close()

On line 3, we satisfy the contract by providing an implementation for it.

override fun close() = throw IOException("${hashCode()} throwing from close()") //3

Line 2 is not part of the Autocloseable contract, but it is there only to show how Suppressed Exceptions are propagated.

We can now use this Test class in main() with the use() function like below.

Test().use { //1
   it.doThrow() //2
}

On line 1, we instantiate Test, and because it implements Closeable, we can use the extension function use() from it.

Line 2 is where we purposely throw a RuntimeException just to see whether the suppressed exception shows up.

The code prints the stack trace below.

Exception in thread "main" java.lang.RuntimeException: 1149319664 throwing from doThrow()
    at com.example.Test.doThrow(Entry.kt:20)
    at com.example.EntryKt.main(Entry.kt:8)
    at com.example.EntryKt.main(Entry.kt)
    Suppressed: java.io.IOException: 1149319664 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more

So we indeed do see the IOException thrown from the close() function as a suppressed exception. We can tell whether it is the close() or the doThrow() function that is printing the stack trace from the messages passed to the Exception constructors:

1149319664 throwing from doThrow()
1149319664 throwing from close()

use() function with multiple resources

In the previous section, we were only declaring one resource for the use() function. In Java, it is possible to declare multiple resources in the same try-with-resources statement. I have added this method into the JavaTest file to show that it is valid Java syntax and will compile.

public void tryWithResources(){
   try(var reader = new BufferedReader(new FileReader(""));
   var reader2 = new BufferedReader(new FileReader("/"))){ //1
       //do nothing
   } catch (IOException e) {
       e.printStackTrace();
   }
}

So how do we do the same thing in Kotlin? Unfortunately, Kotlin does not provide such a construct, so it is not possible, at least not in a clean way like Java’s. There are a lot of discussions around this topic here: https://discuss.kotlinlang.org/t/kotlin-needs-try-with-resources/214/46.

I do not want to divert too much of your attention into unofficial solutions in this tutorial, and I do not think it is a good idea to use them because if Kotlin releases such a language feature in the future, the compiler might not be able to detect and convert these unofficial language features into your code.

To play safe, I would stick with nested use() blocks because I think the compiler can easily find those in the code in the future and assist with refactoring.

It is very simple to use the nested use blocks, which allows you to access both Closeable resources in the same scope. Go ahead and uncomment the previous function calls in main() and add the nested use() blocks below.

Test().use { //3
   Test().use { //4
       it.doThrow() //5
   }
}

The code prints:

Exception in thread "main" java.lang.RuntimeException: 1149319664 throwing from doThrow()
    at com.example.Test.doThrow(Entry.kt:20)
    at com.example.EntryKt.main(Entry.kt:13)
    at com.example.EntryKt.main(Entry.kt)
    Suppressed: java.io.IOException: 1149319664 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more
    Suppressed: java.io.IOException: 935044096 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more

And we can see that Kotlin tried to close both of the resources for us.

Solution Code

JavaTest.java

package com.example;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class JavaTest {
   public void tryWithResource() {
       try(var reader = new BufferedReader(new FileReader(""))){ //1
           //do nothing
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   public void tryWithResources(){
       try(var reader = new BufferedReader(new FileReader(""));
       var reader2 = new BufferedReader(new FileReader("/"))){ //1
           //do nothing
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

Entry.kt

package com.example

import java.io.IOException

fun main(){

//    Test().use { //1
//        it.doThrow() //2
//    }

   Test().use { //3
       Test().use { //4
           it.doThrow() //5
       }
   }
}

class Test: AutoCloseable { //1

   fun doThrow(): Nothing = throw RuntimeException("${hashCode()} throwing from doThrow()") //2

   override fun close() = throw IOException("${hashCode()} throwing from close()") //3

}

Summary

We have learned how to utilize the inline function use() in Kotlin to automatically close resources in places where we normally would use a try-with-resources in Java. The full project code can be found here: https://github.com/dmitrilc/DaniwebKotlinTWRuse