⏹️ About making your JVM application interruptible

Today we’ll discuss how to make your JVM application interruptible, what exactly it means, why you would need it, and how it can improve your development experience.

What does “make an application interruptible” mean?

Basically, it means that your application handles java.lang.InterruptedException by initiating shutdown and releasing all resources. Nowadays, it’s rare for people to make their applications and frameworks interruptible, probably because it doesn’t affect production and usually doesn’t heavily affect the development process.

Why do I need to implement it then?

There are use cases when your application starts in an already existing JVM process, usually for optimization purposes. It could be your build system, IDE, or some other tool that is responsible for running an application for some reason. If that’s your case and it’s important to you to use this tool or method of running, then you need your application to be interruptible.

In general, there are at least two ways to run a JVM application during development. Let’s compare them.

Standalone Java process

This is the case when an application starts in its own Java process. This is what happens when we set fork := true in sbt, how Gradle runs your application, and how it runs in production.

When fork flag is set, running sbt run starts your application in a completely separate process, which is not related to sbt in any way.

A notable downside of this approach is that it makes everything a little bit slower because the JVM starts from scratch, loads all the classes, nothing is JIT-compiled yet, and so on.

But the advantage is that it allows you to handle OS signals, like SIGTERM, SIGINT, SIGUSR2, and others. Catching these signals is what frameworks do for you, and they rely on them to initiate shutdown logic, which is usually implemented using Runtime.addShutdownHook(Thread). In a common scenario, frameworks set shutdown hooks that are triggered when the JVM process stops, for example, when a container stops in production or when you press CTRL + C to cancel a forked run.

As an additional advantage, running an application in a separate process better shows how it would run in production.

Run within existing JVM process

As opposed to running an application in a separate process, during development you can also run it in an already existing JVM.

This is the default behavior for sbt run and it makes everything a little bit faster because you don’t need to start another JVM, system classes are already loaded, many optimizations are already applied, and so on.

However, as you might have guessed, you can no longer catch OS signals, and shutdown hooks won’t be called either. When you press CTRL + C, sbt throws java.lang.InterruptedException in your application’s main thread and expects it to stop. If your application or framework doesn’t handle it correctly, you may encounter memory leaks, unreleased ports, strange behavior in subsequent runs, or perhaps your CTRL + C will be completely ignored, among other unpleasant issues. This is not a rare case.

There is even an open issue in sbt where people advocate for making “fork := true” the default because their application doesn’t work well in a non-forked JVM. This mostly happens because their framework (or perhaps the application itself) incorrectly handles InterruptedException, which is clearly a bug. You can see quotes such as:

@jrudolph said: I have to say, that adding fork in run := true to an sbt project is one of the first things I do in many cases.

@AugustNagro said: I just spent 30 minutes trying to figure out why a Vertx endpoint was not being updated.

@altrack said: From my team’s experience we had more issue than gains from having fork := false, and eventually converted all dozens repos to fork := true. Usually we had some strange startup/shutdown errors/issues that were resolved by adding fork =: true.

Such issues are possible not only with the sbt runner but also with any other tool that “wraps” your application and restarts it within a single JVM process.

Is it worth? Well, it’s debatable and is completely up to you, but if you clearly don’t need the provided development speed boost, or if it’s unnoticeable, then don’t bother yourself. Should your framework follow these interruptible rules? Yes.

How to make an application “interruptible”?

Well, there is no universal solution, and every case must be investigated individually, but a very basic example is:

// Before
public class Application {
  public static void main(String[] args) {
    new MyWebServer("localhost", 8080).start();
  }
}

// After
public class Application {
  public static void main(String[] args) {
    var server = new MyWebServer("localhost", 8080);
    try {
      server.start();
      Thread.currentThread().join();
    } catch (InterruptedException ex) {
      server.close();
    }
  }
}

The general idea is to shut down everything if you catch InterruptedException - that’s it. Also, ensure that your main method runs all the time while the application is alive, as it will make your lifecycle even more predictable for external tools.

Conclusion

Thank you for reading this little article, I hope it will be useful to someone. Feel free to reach me if you have something to say.

And also take a look on the posts on similar topics: