Catching exceptions with less code in Kotlin

A tiny inline function for consise try/catch

In JVM every call can throw an exception. In Java language, we have dedicated declaration that a method is expected to throw some exception types. But still, some other (Throwable, Error, RuntimeException) exceptions may still be thrown.

In Kotlin there are no checked exceptions (like, say in C#). Sill, one have to expect any possible exception being thrown from any possible place.

Most cases it’s ok and one should not do anything specific about exceptions. Still, there are other places, where an exception may break code logic. In asynchronous applications (for example with RxJava, Netty, grpc-java) where most of the code is a callback, it may turn out necessary to make sure an exception is not breaking some outer login.

A trivial solution is to use try/catch. But it makes a code quite ugly when you have several statements to call. It may look like that

 try {
   callAction1()
 } catch(t : ExceptionType) {
   handleAction1Error()
 }
 try {
   callAction2()
 } catch(t : ExceptionType) {
   handleAction2Error()
 }
 /// ....
 try {
   callActionN()
 } catch(t : ExceptionType) {
   handleActionNError()
 }

A good think here is that try/catch is expression in Kotlin, but still it is quite long to use.

In my application, I found that about 80% of such catches were done to log actual problem and to continue forking further. Meaning handleActionNError() functions in my case were calls to a Logger.

There is now equivalent way in Java to replace this long construction. Of course, it is possible to pass a lambda expression into a function like catchAll. But this would change a program and it would add extra object creation in most of the cases.

One can implement similar catchAll function in Kotlin too. Thanks to inline functions it has no overhead at all.

This is the function I use:

inline fun catchAll(LOG: Logger, message: String, action: () -> Unit) {
  try {
    action()
  } catch (t: Throwable) {
    LOG.warn("Failed to $message. ${t.message}", t)
  }
}

So now I may rewrite the above example in a way consise fashion

 catchAll(LOG, "action1") {
   callAction1()
 }
 catchAll(LOG, "action2") {
   callAction2()
 }
 ///  ....
 catchAll(LOG, "actionN") {
   callActionN()
 }

Every usage of the function catchAll is inlined by Kotlin compiler in to a caller method bodies. Kotlin compiler also inlines the action anonymous function action too. There is no overhead! Let’s consider the following example:

fun main(args: Array<String>) {
  catchAll(LOG, "println") {
    println("Test console output")
  }
}

The following bytecode is generated out of it. Note. I use IntelliJ IDEA 2017.1 EAP with Kotlin 1.0.6 plugin. The generated bytecode may change with a future version of tools.

public final static main([Ljava/lang/String;)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
    TRYCATCHBLOCK L0 L1 L2 java/lang/Throwable
   L3
    ALOAD 0
    LDC "args"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L4
    LINENUMBER 17 L4
    GETSTATIC LOG.INSTANCE : LLOG;
    CHECKCAST Logger
    ASTORE 1
    LDC "println"
    ASTORE 2
    NOP
   L5
    LINENUMBER 24 L5
   L6
   L0
    NOP
   L7
    LINENUMBER 25 L7
    NOP
   L8
    LINENUMBER 18 L8
    LDC "Test console output"
    ASTORE 3
    NOP
   L9
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L10
   L11
    LINENUMBER 18 L11
   L12
    LINENUMBER 19 L12
   L13
    NOP
   L1
    GOTO L14
   L2
    LINENUMBER 26 L2
    ASTORE 3
   L15
    LINENUMBER 27 L15
    ALOAD 1
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Failed to "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC ". "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 3
    INVOKEVIRTUAL java/lang/Throwable.getMessage ()Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ALOAD 3
    INVOKEVIRTUAL Logger.warn (Ljava/lang/String;Ljava/lang/Object;)V
   L16
    LINENUMBER 28 L16
   L14
    LINENUMBER 29 L14
   L17
   L18
    LINENUMBER 20 L18
    RETURN
   L19
    LOCALVARIABLE $i$a$1$catchAll I L8 L13 4
    LOCALVARIABLE t$iv Ljava/lang/Throwable; L2 L16 3
    LOCALVARIABLE LOG$iv LLogger; L5 L17 1
    LOCALVARIABLE message$iv Ljava/lang/String; L5 L17 2
    LOCALVARIABLE $i$f$catchAll I L5 L17 5
    LOCALVARIABLE args [Ljava/lang/String; L3 L19 0
    MAXSTACK = 3
    MAXLOCALS = 6

As we see the catchAll function call is inlined. We have println call as-is, without any anonymous function wrappers. Any combination of catchAll calls generates similar bytecode with try/catch blocks. Once can easily combine such calls to make a program easier to read on some higher level.

Disclaimer. Checked or unchecked exceptions are meaningful. I’m not trying to promote the idea to catch all possible exceptions in every possible statement. The goal is to show it is possible to create a tiny function that would help to recure a number of similar code snippets and improve readability. It is up to you to decide if an error is OK to ignore or to log without propagation.

comments powered by Disqus