Running Play Framework on NixOS using JDK 11

Good morning! First, some personal news: I'm switching to NixOS, and I'm kind of excited about it, so expect some articles to that effect. Today, I'm celebrating the ease with which I got a Play Framework environment up and running, including installing sbt and downgrading to JDK 11.

As a software developer, I do not at appreciate having to fight with my environment, so when I heard that NixOS uses a purely functional, declarative approach to maintaining packages, I was interested. Recently, my approach maintaining a clean desktop has revolved around docker, so I'm open to less hacky approaches for sure.

The real test is sbt, which in my experience is a finicky piece of software. Anything with a Java dependency can get complicated already, and sbt is particularly stateful as it downloads its dependencies on the fly. So far I haven't been successful getting the Docker-and-alias trick above to work.

Installing sbt on NixOS

...is pleasantly trivial. There is an sbt package in the official repository which can be installed in the usual NixOS way in the packages list in the /etc/nixos/configuration.nix file.

/etc/nixos/configuration.nix

  ...

  environment.systemPackages = with pkgs; [
    docker
    emacs
    firefox
    git
    gnome.gnome-tweaks
    sbt
    wget
  ];

  ...

Then, to make the change take effect, run sudo nixos-rebuild switch.

After this, I can run sbt against my existing project and it works smoothly.

But it's not compatible with Play at runtime.

UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item

When I run the application, it serves exceptions:

console output

--- (Running the application, auto-reloading is enabled) ---

p.c.s.AkkaHttpServer - Listening for HTTP on /[0:0:0:0:0:0:0:0]:9000

(Server started, use Enter to stop and go back to the console...)

2022-07-04 12:46:00 ERROR p.api.http.DefaultHttpErrorHandler

! @7o7k89hhe - Internal server error, for (GET) [/] ->

play.api.UnexpectedException: Unexpected exception[UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item]
    at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:254)
    at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:148)
    at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:302)
    at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:224)
    at akka.stream.impl.fusing.MapAsync$$anon$30.onPush(Ops.scala:1307)
    at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:542)

...

stack trace from error page

com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item
     com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2051)
     com.google.common.cache.LocalCache.get(LocalCache.java:3962)
     com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3985)
     com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4946)
     com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4952)
     com.google.inject.internal.FailableCache.get(FailableCache.java:54)
     com.google.inject.internal.ConstructorInjectorStore.get(ConstructorInjectorStore.java:49)
     com.google.inject.internal.ConstructorBindingImpl.initialize(ConstructorBindingImpl.java:155)
     com.google.inject.internal.InjectorImpl.initializeBinding(InjectorImpl.java:592)

Both stack traces given go on for ages winding through various Play, Akka, Google, and even core Java libraries, far downstack from the actual application code.

Play Framework Compatibility

A quick search tells me that other people have this problem when running Play Framework against incompatible Java versions and that Play Framework 2.8 (the current version at the time of this writing) is compatible with JDK 8 and JDK 11. Sure enough, when I watched sbt startup, I saw that it used JDK 17, so I knew I needed to downgrade to JDK 11.

I tried installing the JDK 11 package using the same /etc/configuration.nix, but of course it's a feature of Nix that that's not how that works--each package comes with it's own dependencies, and sbt uses JDK 17 still.

So the question is: How do I downgrade the JDK version that sbt uses from JDK 17 to JDK 11?

And this makes me a little itchy. Is this going to be hard? Am I going to have to fork the sbt package? Because the reason why I switched to NixOS in the first place is to avoid going to such lengths.

Downgrading the JDK version used by sbt

It turns out it's easy. The Java version used by the sbt package can be overridden declaratively within that same line in the configuration file.

/etc/nixos/configuration.nix

  ...

  environment.systemPackages = with pkgs; [
    docker
    emacs
    firefox
    git
    gnome.gnome-tweaks
    (sbt.override { jre = pkgs.jdk11; })
    wget
  ];

  ...

Then just nixos-rebuild switch and you're good to go!

In Conclusion... I think I like this!

On the one hand, I've been an Ubuntu user for almost ten years. I know how to be productive there. When things break, I can generally fix them, and if I can't, I can generally figure out what would be good search terms to start with. On the other hand, I think I'm just getting a glimpse of just how powerful nix really is. If I can make a perfectly reproducible Scala environment with a single line, that alone might be worth sticking around for. More to come, I'm sure.