I was just about to close my laptop for the year. Seriously. I had the “Out of Office” email drafted, my notifications were paused, and I was ready to pretend I didn’t know what a stack trace was for at least four days. Then the ecosystem decided to drop a massive pile of updates right on my doorstep.

It’s always like this, isn’t it? You get comfortable with your toolchain, and suddenly GlassFish hits a major milestone, Spring Shell pushes a Release Candidate, and TornadoVM reminds you that your CPU is boring. I’ve spent the last 48 hours digging through release notes instead of sleeping, so you don’t have to. Let’s talk about what just landed and why some of this actually matters for your production code.

GlassFish 8.0: Not Just a Reference Implementation Anymore?

For the longest time, I treated GlassFish as that thing you use to verify Jakarta EE compliance before deploying to “real” servers like WildFly or Open Liberty. But GlassFish 8.0 feels different. It’s leaner. It starts up faster. And honestly? It’s kind of nice.

The big deal here is full Jakarta EE 11 support (or at least the finalized profile bits we care about). If you’ve been holding off on migrating your old javax namespaces because it seemed like a hassle, GlassFish 8 is practically begging you to just get it over with. I tried spinning up a simple REST endpoint using the new profile, and the boilerplate is almost gone. It’s refreshing.

Here’s a quick snippet of what a modern Jakarta REST resource looks like running on GF 8. Notice the lack of XML configuration. I haven’t touched a web.xml in two years and I don’t plan to start now.

package com.example.api;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;

// Records are your friend. Use them.
public record SystemStatus(String service, String status, long uptime) {}

@Path("/status")
public class StatusResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<SystemStatus> getStatus() {
        // Look ma, no heavy DTO classes
        return List.of(
            new SystemStatus("Database", "UP", System.currentTimeMillis()),
            new SystemStatus("Cache", "WARMING", 1200L)
        );
    }
}

Is it going to replace your heavily tuned Quarkus setup tomorrow? Probably not. But for standard EE workloads, it’s proving that the “reference implementation” isn’t just a dusty museum piece.

GPU circuit board - GPU Server PCB Manufacturing
GPU circuit board – GPU Server PCB Manufacturing

Spring Shell 4.0 RC: CLIs Are Cool Again

I have a confession: I hate building web UIs for internal tools. I despise CSS. I hate centering divs. If I can build a CLI tool for the ops team, I will do it every single time. That’s why Spring Shell 4.0 RC caught my eye.

The 4.0 release candidate cleans up a lot of the interaction model. The testing support is finally decent—mocking the shell context used to be a nightmare that made me want to pull my hair out. Now, it’s actually testable. They’ve also improved the GraalVM native image integration, which is critical. Nobody wants to wait 4 seconds for a CLI tool to start up just to run a database migration.

I threw together a quick admin command last night to test the new argument parsing. It handles the help generation automatically, which saves me from writing documentation that nobody reads anyway.

package com.admin.cli;

import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class UserAdminCommands {

    @ShellMethod(key = "nuke-user", value = "Deletes a user and wipes their data. Dangerous.")
    public String deleteUser(
        @ShellOption(help = "The user ID to delete") String userId,
        @ShellOption(defaultValue = "false", help = "Skip confirmation") boolean force
    ) {
        if (!force) {
            return "Whoa there! Use --force if you really mean it.";
        }
        
        // Imagine actual deletion logic here
        // verifyUserExists(userId);
        // deleteData(userId);
        
        return "User " + userId + " has been obliterated. Hope you had backups.";
    }
}

The best part? You compile this to a native binary, scp it to your bastion host, and you look like a wizard. No browser required.

TornadoVM and the Hardware Lust

Then there’s TornadoVM. If you haven’t looked at this recently, you’re missing out on some serious performance wizardry. The premise is simple: Java on GPUs (or FPGAs, if you’re fancy). The reality used to be a configuration hellscape of drivers and OpenCL headaches.

Java code on screen - Writing Less Java Code in AEM with Sling Models / Blogs / Perficient
Java code on screen – Writing Less Java Code in AEM with Sling Models / Blogs / Perficient

The latest updates seem to smooth out the driver discovery process significantly. I ran a matrix multiplication test—classic benchmark, I know, but it works—on my laptop’s humble GPU. The speedup over the standard CPU stream was laughable. We’re talking orders of magnitude.

What I love is that you don’t have to write CUDA. You write Java. The annotation processor does the heavy lifting. It feels like cheating.

import uk.ac.manchester.tornado.api.annotations.Parallel;
import uk.ac.manchester.tornado.api.TaskSchedule;

public class GpuMath {

    // This method gets compiled to OpenCL/PTX automatically
    public static void compute(float[] a, float[] b, float[] result) {
        // The loop must be independent to be parallelized
        for (@Parallel int i = 0; i < result.length; i++) {
            result[i] = a[i] + b[i] * 2.0f; // Simple vector math
        }
    }

    public static void main(String[] args) {
        final int size = 1024 * 1024 * 16;
        float[] a = new float[size];
        float[] b = new float[size];
        float[] result = new float[size];

        // Initialize arrays...
        
        // Define the task graph
        new TaskSchedule("s0")
            .task("t0", GpuMath::compute, a, b, result)
            .streamOut(result) // Copy data back from GPU to host
            .execute();
            
        System.out.println("Computed " + size + " elements on hardware.");
    }
}

Is it production-ready for your average CRUD app? No. Please don’t run your user registration logic on a GPU. But for batch processing or heavy math? It’s a no-brainer.

The Rest of the Pile: WildFly 39 Beta & Hibernate

I can’t ignore the other updates. WildFly 39 Beta is out. I’ve always had a soft spot for WildFly (JBoss days, anyone?). The beta seems stable enough, mostly focusing on tightening up the MicroProfile implementations. It’s solid, reliable, and boring in the best possible way. I deployed a test WAR to it this morning and it just worked. No drama.

And of course, Hibernate and Kotlin are doing their dance. The interoperability is getting smoother. I remember when using Hibernate with Kotlin data classes was a minefield of “open” plugins and proxy errors. Now? It mostly behaves. The latest Hibernate updates seem to handle Kotlin nullability semantics a bit more gracefully, which saved me from at least three NullPointerException crashes in my testing.

So, What Now?

If you’re asking me where to spend your time this weekend, grab Spring Shell 4.0 if you want to build something fun, or GlassFish 8 if you need to future-proof your resume for the Jakarta EE wave. As for me? I’m going to close my IDE, step away from the keyboard, and try to forget that my backlog exists. At least until January 2nd.