Looking at Java 21: Feature Deprecations

 ยท 8 min
Kevin Jarnett on Unsplash

Even though Java is one of the most backward-compatible languages and environments I’ve ever worked with, there’s always the possibility of feature deprecations and even removals.

Java 21 will deprecate two features, so that’s what we’re looking at today.


Why Deprecate Features at All?

To deprecate code or a feature means that its usage is discouraged, and may cease to exist in a future release. Why it’s discouraged can be based on many reasons.

The most common reasons for deprecation are:

  • It has been superseded by a (hopefully) better alternative.
  • There’s a design flaw, it might even be dangerous to use. But due to backward compatibility, it can’t be removed right away, or at all.
  • It’s considered superfluous and should be removed to simplify the system and how to use it.
  • Future updates will make it impossible/impractical to support the old feature/code.

Regardless of the underlying reason, deprecated features are still part of the system, and therefore, usable. At least for now.


Deprecating Windows 32-bit x86 Port

JEP 449 is about deprecating the 32-bit x86 support for Windows, with the ultimate goal to remove it completely in the future.

The reasons behind this deprecation and its future removal are mostly technical.

Windows 32-bit Support

Providing software for any system always requires decisions on which platforms you actually want to support. Targeting a no longer supported platform or version is possible, but usually means an increased support effort, back-porting, fixing stuff yourself, etc.

In the case of the Windows platform, the last 32-bit version was released in 2020, and the official support for it ends on October 2025.

If you know how 64-bit Windows handles 32-bit applications, you might wonder why you can’t just run the JVM through Windows’ integrated WOW64 emulation layer? Well, it’s generally possible to run applications that way, but only with dramatic performance degradation. That’s why the OpenJDK team decided to go forward with the deprecation, as it only affects future versions of Java. Legacy systems can still use all Java versions up to the removal, which hasn’t even happened yet.

One immediate change in Java 21, though, affects the build process of the JDK, as the possibility to configure the build was disabled by default. Trying to run bash ./configure will result in an error:

...
checking compilation type... native
configure: error: The Windows 32-bit x86 port is deprecated and may be removed in a future release. \
Use --enable-deprecated-ports=yes to suppress this error.
configure exiting with result code 1

As the feature is only deprecated, but not removed, the OpenJDK team added the new configuration option, as the error indicated, --enable-deprecated-ports=yes to still allow configuration. However, a warning is produced to highlight the deprecation and possible future removal.

$ bash ./configure --enable-deprecated-ports=yes
...
checking compilation type... native
configure: WARNING: The Windows 32-bit x86 port is deprecated and may be removed in a future release.
...
Build performance summary:
* Cores to use:   32
* Memory limit:   96601 MB

The following warnings were produced. Repeated here for convenience:
WARNING: The Windows 32-bit x86 port is deprecated and may be removed in a future release.

Virtual Vs. Kernel Threads

Java 21 is packed with awesome new features, and the addition of Virtual Threads (JEP 444) is one of them. It introduces lightweight (virtual) threads, which might entirely change how we deal with high-throughput concurrent applications in Java significantly by reducing the required effort to write, maintain, and observe such applications. They have significantly less overhead than traditional platform (kernel) threads.

On Windows 32-bit x86, however, this feature has to fall back to kernel threads due to technical limitations. Such missing functionality of the underlying platform is often a strong indicator for future deprecations and removals.

Still, you can write and use the new threading code, but the expected benefits are missing in action.

Deprecated, But Not Removed (Yet)

As you can see, the deprecation makes sense, as Windows 32-bit x86 is running on fumes anyway. Also, building for the specific platform is still possible, just discouraged, for now. So if you still need to support a legacy system and know what you’re doing and what the ramifications are, you can still use it.


Disallowing Dynamic Loading of Agents

Agents use the Instrumentation API to modify an existing application by altering the bytecode that’s already loaded in the JVM. This gives you the power to alter your application’s behavior without actually changing its source code. It’s often used for profilers and monitoring tools (like Datadog and YourKit), aspect-oriented programming, and more.

How to Load an Agent

There are two ways to load an agent, either statically, by adding the parameter -javaagent:agent-to-load.jar or -agentlib:options to the java call, or dynamically from another application by running code like this:

java
import java.lang.management.ManagementFactory;
import com.sun.tools.attach.VirtualMachine;

public class DynamicAgentLoader {

  public static void main(String... args) {

    int pidOfOtherJVM = ...;
    File agentJar = ...;

    try {
      VirtualMachine vm = VirtualMachine.attach(pidOfOtherJVM);
      vm.loadAgent(agentJar.toAbsolutePath);

      // ... do your work

      vm.detach();
    } catch (Exception e) {
      // ...
    }
  }
}

The first option isn’t much of a problem. It’s a clear and intentional use of a JVM agent. The latter, however, is indirect and possibly out of the control of the JVM being connected to.

The Problem With Dynamic Loading

The Java platform strives towards integrity by default to provide us with a robust solid foundation to build our applications on. Agents were designed with the best intentions in mind, giving you the power of (benign) instrumentation. To assure this integrity, though, instrumentation via (dynamic) Agents is a big problem, as they are out of your direct control and could potentially wreak havoc on your application. That’s why you as the application’s owner must make a conscious and explicit decision about which agents to allow and load.

In Java 21, you can still load dynamic agents, but the JVM produces multiple warnings informing you about the underlying problem and how to hide these warnings:

WARNING: A {Java,JVM TI} agent has been loaded dynamically (file:/path/to/agent.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release

Future Java versions will disallow loading dynamic agents by default, and any use of the Attach API will through an Exception:

com.sun.tools.attach.AgentLoadException: Failed to load agent library: \
Dynamic agent loading is not enabled. Use -XX:+EnableDynamicAgentLoading \
to launch target VM.

The exception message includes the steps required to enable dynamic agent loading: the parameter -XX:+EnableDynamicAgentLoading. So if you make a conscious decision that you want to allow dynamic agents, you still can.

Disabling Dynamic Loading Right Now

So far, only warnings are emitted. However, you can disallow the dynamic loading of Java agents completely. You can do this either to harden your application or to prepare for the upcoming changes, by using the parameter -XX:-EnableDynamicAgentLoading which swaps the + (plus) with a - (dash/minus).


Conclusion

The deprecations of the two mentioned features mentioned in this article make sense to me.

Windows 10 32-bit x86 support is a technical debt that prevents innovations, like utilizing the full power of virtual threads.

Dynamically loading agents scratches hard on the Java platform integrity, and is a potential security risk. However, if an attacker is “close enough” to attach to another JVM, you might have bigger problems.

Nevertheless, we always must be aware of what might change or be removed in the future, as we most likely won’t have any sway on when exactly it happens. Java is usually quite generous with deprecation and removal time frames, with some features might be deprecated for decades, without a removal in sight. So naturally, the question arises if we should use deprecated API or not.

In my opinion, we should try to avoid using deprecated API, if possible. It’s becoming technical debt that accumulates over time, and finally, must be paid off. Nothing is more stressful than needing to upgrade your code for unrelated reasons, and some deprecated feature you relied on for years got finally removed, making the upgrade way more complicated than it needed to be.


A Functional Approach to Java Cover Image
Interested in using functional concepts and techniques in your Java code?
Check out my book!
Available in English, Polish, Korean, and soon, Chinese.

Resources

Looking at Java 21