Java will never become a player in CLI tooling until build packaging becomes first class. Go, Rust, and other languages are so common because their build tooling is dead simple. You can easily get a single file distributable in a single command. go build, cargo build, dotnet publish —-self-contained.
Java on the other hand makes it impossible to get a single distributable. There is no way to get your jar + the vm into a binary. You could use graal native image, but build times are super slow, use too many resources, and it’s non-trivial to get working.
Build tooling in the Java ecosystem just isn’t good enough.
I think the python counterexample speaks a lot. A lot of languages "hide" their footprint in /usr/local or in a venv somewhere; out of sight, out of mind.
The JVM installs cleanly and is self contained, but any artifacts, by default, are not shared system wide as this _always_ have been seen as a security risk. The hot term for it today is "supply chain attack".
Instead, most Java programs tow their dependencies, giving it a bloated feel because its all just there, present in front of you, stored and running as your own user.
I know Python has been big in the space for longer than uv's existence, but uv (https://docs.astral.sh/uv/) has made Python packaging dead simple to me
I don't think uv makes distribution simple? Unless I've missed something, it doesn't do anything out of the box to help you produce a standalone artifact - it builds wheels but those are only useful for a user that already has python and pip, and don't do anything to deal with Python version drift etc.
uv can install a version of python of your choosing in addition to pulling the specific versions of libraries specified in your lockfile. it's extremely dummy-resistant.
Right, but that means the end user has to have (or install) uv, and then you ship them all your code, and then they can use uv to run that. That's a development workflow - and exactly what I meant when I said that uv didn't solve distribution in the way a language like Go or Rust does by producing a single binary.
Yeah it’s funny how “Java runs everywhere” was a huge selling point of the JVM. But now it’s not even included in macOS by default, so if you want somebody to use you Java/clojure CLI they have to install Java. And that will raise eyebrows and make people think “what is this, 2010??”
Bundling the JRE in the bundle typically results in something that is not redistributable with the default OpenJDK license: The Java ecosystem is heavily tilted towards the Apache license, but Hotspot is licensed under the GPL v2 only (no classpath exception). The Apache license and older GPL versions (before 3) are generally assumed to be incompatible.
Every modern openjdk build is licensed as GPLv2 + classpath exception. That exception includes hotspot, since it's part of the jvm. That exemption allows shipping the JVM with your app or linking to it. Otherwise a bunch of enterprise software couldn't exist.
You're asking about a fundamentally different thing.
An app bundle (.app, .rpm, .deb, .msi/.exe etc.) are things jpackage can build for you and are a single shippable artifact for a user with a JRE included so they don't need to do that. It's designed to make it easy to ship Java applications around.
I'm not asking about a fundamentally different thing. The success of other languages isn't because they produce installers. Have you tried native image for a non-trivial application? I've been using it since it came out; I was the first adopter of native-image for Quarkus on Windows. I even wrote the documentation for it at the time. It is not trivial to use, the compile times are extremely long, and the resources it requires are sometimes more than a developers machine can provide.
Or use a graal to build a native-image and ship that around.
But that's not what people want. They want an .exe or a .app, or .rpm, or whatever. That's a container for holding that .jar and it is platform specific and there is no workaround to that problem.
java took a hit on this move by Java and Oracle...and the way they still provide a java executable that tells you to download from somewhere else is really annoying - but you do realize Apple removed python from Mac in 2024 and never distributed python3, right?
So will we see python stop being used?
No, because instead of sitting in corner and mope about it the ecosystem just kept working and filling in the gaps.
Java ecosystems done lot of that - but general public keep having the old changes stuck in their mind.
Java SHOULD never become a player in anything more until Oracle stops being such a threat. Oracle just wants to be a parasite on companies that actually build.
I gave up on Java when Oracle took over, because I thought that it was such a horrific move, but, to their credit, they haven't ruined it for everyone (yet)
They've kept it alive, allowed it to grow, and innovate, even let Green threads back in.
I'm not planning on going back to Java, but that's no longer because Oracle.
I worked at a couple of startups that were mostly Java based and had several CLI tools. The focus was building "fat jars" then running them with "java -jar ...", or running scripts that did that. The Java VM was a system dependency and getting it baked into the binary just wasn't a practical concern.
I work at a startup that ships a Java cli to our clients. It is a giant pain in the butt. There are constant support requests from users that are using the wrong version of Java, too old or too new. Sometimes they have to wait weeks for authorization to even install the Java runtime. IT departments are extremely strict about installing Java.
I could see how that could be annoying. My experience was with internal apps whee we managed all the infrastructure. Some IT departments are often extremely strict about installing anything. Some won't even let you access a web site without it being proxied through something like ZScaler.
> IT departments are extremely strict about installing Java.
Understandably so, given that some Java runtimes (most notably, Oracle's) require a paid license for commercial use. Having users installing that can get the company in hot water.
The point is, there are legal complexities which make it unsafe for an employee to go out on their own and download a JRE - sure, they might download Adoptium and be fine, but they also might download one of the ones which requires a commercial license. An IT department isn't going to be comfortable with that risk.
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 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.
> There is no way to get your jar + the vm into a binary.
My text editor, KeenWrite[1], offered binaries for Linux, macOS, and Windows. The Windows binary was axed due code signing costs and requiring third-party builds, rather than any technical issues with cross-platform packaging.
One way is to create self-extracting executable binaries using a tool such as warp[2]. I've built an installer script[3] (install.sh) to create platform-specific launchers. Running `time keenwrite.bin --version` on Linux shows 0.327s; after the first run, subsequent launches are quick.
Non-trivial, doesn’t work with standard build tooling, and unless something has changed it produces installers that extract into several different files. You don’t just get a standalone statically linked binary that you can hand off.
It's not dynamic linking, despite excellent support for very late binding in historic Java versions. (Newer versions require specific launcher configurations to use certain platform features, which breaks late loading of classes that use those features.)
jar + vm into single binary feels like a solvable problem.
I haven't really had the burning need for app+jvm since I used graalvm and made jbang and its now trivial to run app + shared jvm for me - but I can see the utility for it.
Noted down the various pointers and existing attempt to explore in 2026 !
About build and publishing - this I feel is also solvable.
Especially if we stop trying to solve it for all possible mutations of gradle/maven builds and just make it work for jars...this is where jbang/jreleaser really simplifies more than I think many realize.
So in 2026 I'll definitely try get more recipes published on this and also see if we can make something like `jbang publish` "Just Work"
Java on the other hand makes it impossible to get a single distributable. There is no way to get your jar + the vm into a binary. You could use graal native image, but build times are super slow, use too many resources, and it’s non-trivial to get working.
Build tooling in the Java ecosystem just isn’t good enough.