Why Do Java Programmers Cry When Switching to Kotlin?
\n\nImagine: you're writing code in Java, you open a file, read data, and then... forget to close it. Sound familiar? In Java, the try-with-resources construct was invented for this, which automatically calls close(). But when developers switch to Kotlin, they often get stuck: "Where is my beloved try (Resource res = ...)?"
Spoiler: Kotlin does not have try-with-resources as a separate language construct. And that's good news. Instead, the language offers a more elegant and concise solution — the use function. Let's figure out how it works and why your code will become cleaner.
The Problem: Resources Need to Be Closed
\n\nAny external resource — a file, a network connection, a database — requires manual management. If you open a FileInputStream and don't close it, the system can "leak" (memory leak), and the file will remain locked for other processes.
In Java, this problem is solved in two ways:
\n\n- \n
- Old way: a
finallyblock with an explicit call toclose(). \n - New way:
try-with-resources(introduced in Java 7). \n
Kotlin went further: it uses extension functions and lambdas to make code not only safe but also beautiful.
\n\nWhat Does try-with-resources Look Like in Java?
\n\nFor comparison, let's look at classic Java code:
\n\ntry (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {\n String line = br.readLine();\n System.out.println(line);\n} catch (IOException e) {\n e.printStackTrace();\n}\n// br is closed automatically\n\n\nIt's all good, but there are nuances:
\n- \n
- You need to declare a variable inside the
tryparentheses. \n - Only classes implementing
AutoCloseableare supported. \n - The code becomes cumbersome if there are multiple resources. \n
The Kotlin Approach: The use Function
\n\nIn Kotlin, the use function is used for safe resource management. It is defined as an extension for the Closeable interface (and AutoCloseable for Java compatibility).
Here's what the same example looks like in Kotlin:
\n\nimport java.io.BufferedReader\nimport java.io.FileReader\n\nfun main() {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n // reader is automatically closed\n}\n\n\nClean, concise, without extra try-catch? Not quite. use itself does not catch exceptions. It only guarantees the call to close() at the end of the block. If you need to handle an error, add a try-catch around it:
try {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n} catch (e: IOException) {\n println("Error reading file: ${e.message}")\n}\n\n\nHow Does use Work Under the Hood?
\n\nTo understand the magic, let's look at the Kotlin source code. The use function is defined in the standard library roughly like this:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {\n var exception: Throwable? = null\n try {\n return block(this)\n } catch (e: Throwable) {\n exception = e\n throw e\n } finally {\n when {\n exception == null -> close()\n else -> try {\n close()\n } catch (closeException: Throwable) {\n // suppress the exception, add it to the main one\n exception.addSuppressed(closeExcep