Cover image for Ubuntu 25.10: The Update That Bricked My Java Docker Containers

Photo by thorl5 on Pexels

Ubuntu 25.10: The Update That Bricked My Java Docker Containers

The Quiet Update That Turned Into a Nightmare

It was a Friday night, just like any other. I finally decided to do what every good developer avoids: updating my OS. Ubuntu 24.04 LTS → 25.10, just a routine little update. "It can only get better," I told myself with that specific brand of pessimism reserved for those who have seen too many updates go south (Ubuntu 24.10, I’m looking at you!).

I launched the update with confidence. Everything went well. Reboot. Everything works. Perfect.

Except that the following Monday, my E2E tests were failing. The selenium/standalone-chrome:4.5.3 Docker container, which worked perfectly the day before, now refused to start. It was crashing in a loop with this magnificent error message:

chrome-1  | java.lang.reflect.InvocationTargetException
chrome-1  |      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
chrome-1  |      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
chrome-1  |      at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
chrome-1  |      at java.base/java.lang.reflect.Method.invoke(Method.java:566)
chrome-1  |      at org.openqa.selenium.grid.Bootstrap.runMain(Bootstrap.java:77)
chrome-1  |      at org.openqa.selenium.grid.Bootstrap.main(Bootstrap.java:70)
chrome-1  | Caused by: java.lang.NullPointerException
chrome-1  |      at java.base/jdk.internal.platform.cgroupv2.CgroupV2Subsystem.getInstance(CgroupV2Subsystem.java:81)
chrome-1  |      at java.base/jdk.internal.platform.CgroupSubsystemFactory.create(CgroupSubsystemFactory.java:113)
chrome-1  |      at java.base/jdk.internal.platform.CgroupMetrics.getInstance(CgroupMetrics.java:167)
chrome-1  |      at java.base/jdk.internal.platform.SystemMetrics.instance(SystemMetrics.java:29)
chrome-1  |      at java.base/jdk.internal.platform.Metrics.systemMetrics(Metrics.java:58)
chrome-1  |      at java.base/jdk.internal.platform.Container.metrics(Container.java:43)
...

My first thought:

I'm a PHP developer, not a Java one.

My reflex:

Let's ask someone smarter than me. Gemini cricket (🤖🦗) 🥲.

The Investigation

My cricket friend pointed out cgroupv2 on line CgroupV2Subsystem.java:81 and linked it to my system update. But what exactly is cgroupv2?

🤖🦗:

Long story short, it's what allows Docker 🐋 to limit a container's CPU or RAM.

In concrete terms:

  • Docker tells the JVM: "You are allowed 2GB of RAM."
  • Java reads this info from cgroups (resource management files).
  • Java adjusts its behavior (Heap memory, etc.) accordingly.

This is supposed to be a good thing. It prevents Java from being slaughtered by the host system's OOM Killer. However, Java must parse these files, and that’s where things fall apart.

Why a crash instead of just an error? In the source code of older JVMs, if the path returned by the system interface isn't exactly what's expected, the mountPoint variable remains null. The JVM then attempts to call a method on this non-existent object. It's a classic backfire: the function meant to protect your application becomes the very cause of its summary execution.

The Plot Twist: The Subtle Difference Between Ubuntu 24.04 and 25.10

This is where it gets fascinating.

The real culprit is the evolution of systemd (now at version 258 in Ubuntu 25.10). Since v256, systemd has enforced a "hardening" of the cgroup v2 hierarchy. It no longer just exposes controllers; it organizes them in a much more granular way to isolate services. Legacy Java versions, designed back when the hierarchy was more predictable and less protected, find themselves literally "blind" to this new structure.

And guess what? The old Java initialization logic (pre-Java 17) is far too rigid to understand this new format.

At startup, the Java inside our container scouts the system, fails to find the "memory" controller exactly where it expected, and silently assigns null to its internal variable. On the very next line, the code tries to call the .getMountPoint() method on this empty object.

BOOM. NullPointerException. Instant process death.

The culprit wasn't our code or our Docker config, but an old JVM incapable of adapting to a modern Linux kernel's new hierarchy. Fair enough, you might say.

THE Solution

The clean solution, the one you should always use in production:

# Update to a recent version of the image
docker pull selenium/standalone-chrome:4.20.0

# OR use an image with Java 17+
docker pull selenium/standalone-chrome:latest

The Survival Hack — Disabling UseContainerSupport

If you cannot update the image (legacy constraints, QA validation, etc.), you can disable container detection:

# Option 1: Via Docker environment variable
docker run -d \
  -e JAVA_OPTS="-XX:-UseContainerSupport" \
  selenium/standalone-chrome:4.5.3

# Option 2: Via docker-compose.yml
services:
  chrome:
    image: selenium/standalone-chrome:4.5.3
    environment:
      - JAVA_OPTS=-XX:-UseContainerSupport

⚠️ Important Warning:

  • DO NOT DO THIS IN PRODUCTION. In my case, this is a local development container.
  • Without UseContainerSupport, Java is unaware of its limits and can be terminated by the host system's OOM Killer.
  • This solution is a temporary band-aid while waiting for an update.

The Morale of the Story

Our software ecosystems are fragile. A simple Linux kernel update—via an Ubuntu update in my case—can break containers that worked perfectly from one version to another.

Philosophical advice:

Remembering to "clean your room" regularly (I'm sure you get the metaphor) can save a lot of time.

Never perform an OS update on a Friday.

Always make sure to test your Docker containers in a staging environment after a system update.

"Works on my machine" — until the kernel updates.

Sources and References