I spent most of Tuesday fighting with a Docker image that refused to shrink below 400MB. You know the feeling. You strip the OS, you multi-stage the build, you pray to the gods of compression, and yet—it’s still bloated. It’s enough to make you want to go back to writing Go.

Almost.

But then the new release cycle hit, and I started looking at what BellSoft is doing with Liberica JDK 20. While everyone else is arguing about the syntax sugar in the new Java specification, I’m looking at the runtime. And honestly? This might be the release that finally makes me switch my default base images.

The Musl Advantage (and Why You Should Care)

Most of us default to standard OpenJDK or maybe Amazon Corretto because it’s “safe.” I’ve used Corretto for years—it’s solid, predictable, and AWS supports it. No shade there. But BellSoft has been carving out this weird, hyper-optimized niche with Liberica that targets Alpine Linux users specifically.

If you’ve ever tried to run standard Java on Alpine, you’ve probably hit the glibc vs. musl nightmare. It crashes. It throws obscure linking errors. It haunts your dreams.

Liberica JDK 20 continues their trend of native Musl support. This isn’t just a wrapper; they’re building specifically for it. The result is Alpaquita Linux—their distro—which claims to be smaller than Alpine. I tested a simple Spring Boot app (okay, maybe not “simple,” it had a few too many dependencies) and the startup time difference wasn’t massive, but the footprint? Noticeable.

Docker shipping container concept - The Mental Model Of Docker Container Shipping
Docker shipping container concept – The Mental Model Of Docker Container Shipping

What’s actually in JDK 20?

Let’s talk about the Java features themselves. JDK 20 is a “feature release,” which is code for “don’t run this in production unless you’re brave or bored.” But for those of us experimenting on the side, the updates to Scoped Values are the real story.

ThreadLocals have been a pain for fifteen years. They leak memory like a sieve if you aren’t careful, and passing context between threads is clunky. Scoped Values (JEP 429) in JDK 20 are trying to fix that. It’s cleaner. It’s immutable. It’s what we should have had a decade ago.

Here’s a quick look at how I’m messing around with it in Liberica 20:

import jdk.incubator.concurrent.ScopedValue;

public class ScopedValueTest {
    // Define a scoped value
    final static ScopedValue<String> USER_ID = ScopedValue.newInstance();

    public void handleRequest() {
        // Bind the value only for the scope of the runnable
        ScopedValue.where(USER_ID, "user-12345")
                   .run(() -> {
                       process();
                   });
    }

    private void process() {
        // No passing arguments down the stack!
        System.out.println("Processing for: " + USER_ID.get());
    }
}

It looks simple, but this changes everything for frameworks. Speaking of which, the ecosystem is scrambling to keep up.

The Framework Shuffle: Quarkus, Micronaut, and the Beta fatigue

It’s not just the JDK. The whole stack is in flux right now. I saw that Quarkus dropped 3.0.0.Beta1 recently. This is a big deal because they’re finally moving to Jakarta EE 10 packages. If you’ve been ignoring the javax to jakarta namespace change, your time is up. It’s happening.

I tried upgrading a service to Quarkus 3 last night. It broke immediately. Obviously. That’s on me for using a Beta 1 release, but the migration scripts are actually getting better. If you’re using Hibernate, expect some friction.

Docker shipping container concept - Docker for beginners: a guide to understanding the core concepts
Docker shipping container concept – Docker for beginners: a guide to understanding the core concepts

Micronaut is also pushing 4.0.0-M1. It feels like everyone decided to break compatibility at the same time. It’s exhausting, but necessary. We can’t keep dragging legacy implementations forever.

Helidon and Open Liberty

I don’t use Helidon as much—mostly because I’m stuck in the Quarkus/Spring ecosystem—but seeing version 3.2.0 pop up is interesting. They were one of the first to really push Virtual Threads (Project Loom) hard. With JDK 20 iterating on Loom (second preview), Helidon Níma is looking more and more like the future of high-throughput services. Blocking code that scales like reactive code? Yes, please.

And Open Liberty 23.0.0.3-beta… well, it’s there. IBM’s workhorse keeps chugging along. It’s solid, but it doesn’t excite me the way a sub-50MB container image does.

So, do you upgrade?

Here’s my take: If you are running a monolithic banking app, stay on JDK 17 (or 21, if you’re feeling spicy). Don’t touch JDK 20. It’s a short-term release.

But if you are building microservices and you pay your own cloud bills? Give BellSoft Liberica JDK 20 a shot, specifically the Alpine/Musl builds. The memory savings are real. The startup times are snappy. And with frameworks like Quarkus 3 and Micronaut 4 prepping for the next generation of Java, you might as well get your environment ready now.

Just maybe don’t deploy the Beta versions to production on a Friday. I learned that one the hard way.