I’m not convinced that “single binary” really matters in practice. What actually matters is how easy it is to install, run, and update an application, and that depends entirely on the target user.

For end-user apps, this is basically solved: use jpackage to ship an installable app with a bundled, trimmed JRE. Better yet, distribute via the OS app store so updates are handled for you (on Linux, Flatpak is probably the right answer today).

For CLI tools, you’re already assuming a more technical audience. At that point you have two real options:

- ship everything bundled (Go/Rust static binaries, pyinstaller, jpackage)

- ship just the app and rely on a smart launcher/runtime manager (npx, bunx, uvx, jbang), and assume your technical audience can install that first

The real question isn’t "is it a single binary?", it’s "how do users install, run, and update it?". In practice, that’s already been solved by developer package managers like brew and scoop. All the Go and Rust CLIs on my machine are installed via brew, not manually downloaded from GitHub releases.

You also want CLIs on PATH or inside a dev environment (mise, direnv, etc.), so whether that executable is a true single binary or a symlink to a bundle is mostly irrelevant.

So the trade-off becomes, do you support `brew install foo-java-tool` with a bundled JRE, or do you ask users to `brew install jbang` and then `jbang install foo-tool`? Either way, the end result is the same, you run `foo-tool`.

Note, Claude Code for instance supports both options (curl | bash, brew cask, and npm -i), isn't a single binary, and that still hasn't stopped it from being the most popular CLI tool released this/last year.

There’s definitely room for improvement in Java’s packaging story, I just think the focus shouldn’t be on "single binary" as the primary goal.