Choosing the Right JDK for WildFly and JBoss EAP

This tutorial discusses the recommended JDK versions for WildFly and JBoss Enterprise Application Platform (EAP), as well as how to configure the Java runtime for your deployments. The article is updated to September 2026 and reflects the latest server generations, including WildFly 40/41 and JBoss EAP 8.x.

WildFly and JBoss EAP JDK Compatibility at a Glance

Navigating Java and Jakarta EE compatibility across WildFly and JBoss EAP can be challenging due to rapid release cycles. Here is what you need to know at a glance:

  • WildFly 28–34: Minimum Java 11, recommended Java 17/21 (Jakarta EE 10 baseline). WildFly 34 is the final series supporting Java 11.
  • WildFly 35–39: Minimum Java 17, recommended Java 21 (LTS). Support for Java 11 is completely dropped.
  • WildFly 40–41: Minimum Java 17, introducing Jakarta EE 11 in the standard release (Java 25 recommended) while maintaining a companion Jakarta EE 10 variant (Java 21 recommended).
  • JBoss EAP 8.0: Requires Java 11 or 17 (Java 17 recommended; JDK 8 dropped). Supports Jakarta EE 10.
  • JBoss EAP 8.1: Requires Java 17, with full support for Java 21 (LTS recommended; Java 11 dropped). Supports Jakarta EE 10.

Choosing the right JDK for your WildFly or JBoss EAP deployment isn’t just a matter of basic compatibility—it is a core architectural decision that directly impacts performance, garbage collection efficiency, security, and enterprise support lifecycles. With multiple JDK vendors and rapid Java release cycles, aligning your runtime with official Technology Compatibility Kit (TCK) certifications is essential for maintaining a stable production environment.

Currently, the minimum Java version required for modern WildFly releases (WildFly 35 and newer) is Java 17 (Oracle JDK or OpenJDK).

JDK Compatibility for Each WildFly Version

Each WildFly release defines a minimum JDK requirement and a maximum tested JDK version. You should select a JDK within the supported Min-Max range outlined below:

WildFly Version Min. JDK Max / Recommended JDK Jakarta EE Baseline
15 8 11 Java EE 8
16 8 12 Java EE 8
17–24 8 13 Jakarta EE 8
25–26 8 17 Jakarta EE 8 / EE 9.1
27–31 11 17 (JDK 21 Core) Jakarta EE 10
32–34 11 21 (LTS) Jakarta EE 10
35–39 17 21 (LTS) Jakarta EE 10
40–41 (EE11 Standard) 17 25 (LTS) Jakarta EE 11
40–41 (EE10 Variant) 17 21 (LTS) Jakarta EE 10

Recommended JDK Versions for WildFly

  • WildFly up to 26: Runs on Java 8 or Java 11. Due to extensive test coverage, JDK 11 is the recommended LTS choice for legacy installations.
  • WildFly 27 to 34: Drops support for Java 8, establishing Java 11 as the minimum baseline. While Java 11 is supported through WildFly 34, upgrading to Java 17 or Java 21 is strongly recommended. Note that WildFly 34 is the final release series supporting Java 11.
  • WildFly 35 to 39: Completely removes support for Java 11. The minimum runtime requirement is Java 17, while Java 21 (LTS) is the primary recommended JDK for production workloads.
  • WildFly 40 and 41: Introduces Jakarta EE 11 support for the standard distribution. For standard EE11 builds, Java 25 (LTS) is recommended, while Java 21 remains recommended for the companion WildFly EE10 variant. Note: SecurityManager cannot be enabled when running Jakarta EE 11 builds — see the dedicated section below.

Official Java and Jakarta EE certification matrices can be verified in the WildFly Certifications Repository.

JBoss Enterprise Application Platform (EAP 7 and EAP 8)

To run in a fully supported Red Hat enterprise configuration, JBoss EAP must execute on a Technology Compatibility Kit (TCK) certified JDK implementation on a supported operating system. Below is the supported JDK matrix across JBoss EAP major releases:

JBoss EAP Version Supported JDK Implementations Jakarta EE Baseline
7.1.0 Oracle JDK 1.8, OpenJDK 1.8, IBM JDK 1.8 Java EE 7
7.2.0 Oracle JDK 1.8 & 11, OpenJDK 1.8 & 11, IBM JDK 1.8 Java EE 8
7.3.0 Oracle JDK 1.8 & 11, OpenJDK 1.8 & 11, IBM JDK 1.8, Eclipse OpenJ9 11 Java EE 8
7.4.0 Oracle JDK 1.8 & 11, OpenJDK 1.8 & 11, IBM JDK 1.8, Eclipse OpenJ9 11, Azul Zulu 8 & 11 Java EE 8
8.0 Red Hat OpenJDK 11 & 17, Oracle JDK 11 & 17, Adoptium, Azul Zulu (JDK 8 dropped; Java 17 recommended) Jakarta EE 10
8.1 Red Hat OpenJDK 17 & 21, Oracle JDK 17 & 21, Adoptium, Azul Zulu (Java 21 recommended; Java 11 dropped) Jakarta EE 10

For detailed support lifecycle matrices, refer to the official Red Hat JBoss EAP Supported Configurations KCS Article.

How to Set the Java Version in JBoss EAP or WildFly

Configuring the JDK executable and JVM settings for Standalone mode is done via startup configuration scripts located in the bin folder:

  • Linux/Unix: bin/standalone.conf
  • Windows: bin/standalone.conf.bat

To explicitly define which Java installation WildFly or JBoss EAP should use, set the JAVA_HOME variable inside standalone.conf:

# Specify the explicit path to your target JDK installation
JAVA_HOME="/usr/lib/jvm/java-21-openjdk"

# Customize JVM memory flags and Metaspace parameters
JAVA_OPTS="-Xms512m -Xmx2048m -XX:MaxMetaspaceSize=512m -Djava.net.preferIPv4Stack=true"

For Domain mode configurations spanning multiple host controllers, refer to our detailed guide on How to Configure WildFly in Domain Mode.

Fixing InaccessibleObjectException After a JDK 17+ Upgrade

WildFly's own standalone.conf/domain.conf already inject the --add-exports/--add-opens flags the server itself needs to run under the Java Platform Module System — you don't need to touch those. What breaks after moving from JDK 11 to JDK 17+ is usually application or third-party library code that reflectively accesses JDK-internal classes, since JDK 17 enforces strong encapsulation by default (JEP 403 removed the JDK 9-16 --illegal-access escape hatch entirely). The failure looks like this:

java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible:
module java.base does not "opens java.lang" to unnamed module @4f8e4d13
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:180)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:174)

This is most commonly triggered by older versions of Hibernate ORM's bytecode enhancement, Groovy scripting integrations, object-mapping libraries doing field-level reflection (older Jackson/Gson configurations), or legacy in-house code that walks java.lang/java.util internals. Rather than guessing which module to open, capture the exact module and package from the exception (java.base does not "opens java.lang" above) and add a targeted flag to JAVA_OPTS in standalone.conf:

# Append to JAVA_OPTS in standalone.conf — one --add-opens per reported module/package pair
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.lang=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.util=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.io=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/sun.nio.ch=ALL-UNNAMED"

Treat this as a stopgap, not a permanent architecture: each --add-opens re-widens encapsulation the module system was specifically introduced to close off. Track down and upgrade the offending dependency to a JDK 17-clean release where one exists, and keep the list of flags in version control alongside the deployment so it's visible during the next JDK upgrade review — it's exactly the kind of hidden coupling that turns a routine JDK bump into an incident.

Security & Platform Evolution: SecurityManager and the javax → jakarta Shift

Two platform-level changes affect how you plan a JDK/WildFly upgrade together, not independently:

  • SecurityManager: Deprecated for removal at the JDK level since Java 17 (JEP 411). Jakarta EE 11 goes further and formally removes the SecurityManager integration point from the platform specification itself — WildFly's Jakarta EE 11 distribution (the 40/41 "standard" variant) will refuse to start with -Djava.security.manager set. If you still rely on SecurityManager-based sandboxing for multi-tenant deployment isolation, you must migrate to the WildFly EE10 companion variant in the short term and plan an architectural replacement (container/OS-level isolation, or application-level authorization checks) before EE10 itself reaches end of support.
  • javax.* → jakarta.*: Every WildFly release from 27 onward (Jakarta EE 10 baseline) requires the jakarta.* namespace across your deployment — not just your own code, but every transitive dependency. A single legacy javax.servlet/javax.persistence JAR on the classpath alongside jakarta.* equivalents produces confusing ClassNotFoundException/ClassCastException pairs where both sides print an identical-looking class name. Run mvn dependency:tree | grep -i javax before every WildFly major-version upgrade to catch this before it reaches a running server.

GC Selection in Memory-Constrained Containers: G1GC vs. Generational ZGC

The default collector (G1GC) and Generational ZGC (JDK 21+) make different trade-offs that matter specifically when your WildFly pod has a tight memory limit (1–4Gi is typical for a microservice-style deployment):

  • G1GC targets a configurable pause goal (-XX:MaxGCPauseMillis=200 by default) but can still fall back to a longer stop-the-world Full GC under sustained memory pressure — the exact scenario a tightly-limited container is prone to during a traffic spike. Its memory bookkeeping overhead is lower, which generally makes it the more predictable default when the container limit is small (well under 2Gi) and you have little headroom to spare.
  • Generational ZGC (enable explicitly on JDK 21/22 with -XX:+UseZGC -XX:+ZGenerational; default mode under -XX:+UseZGC on JDK 23+) targets sub-millisecond pauses independent of heap size, which is valuable for latency-SLA-bound WildFly deployments (synchronous request/response APIs with tight p99 targets). It needs more headroom for its concurrent, region-based bookkeeping than G1 — in a container sized right at the edge of what the workload needs, ZGC is more likely to hit allocation stalls than G1 would under the same memory pressure.

In both cases, set -XX:MaxRAMPercentage rather than a fixed -Xmx so the heap scales with the container's actual cgroup limit rather than a value hardcoded for one environment:

# G1GC — reasonable default for small/medium containers (<2Gi limit)
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxRAMPercentage=70.0 -XX:MaxGCPauseMillis=200"

# Generational ZGC — for latency-sensitive workloads with more headroom (JDK 21/22 syntax shown; drop -XX:+ZGenerational on JDK 23+)
JAVA_OPTS="$JAVA_OPTS -XX:+UseZGC -XX:+ZGenerational -XX:MaxRAMPercentage=60.0"

The lower MaxRAMPercentage for ZGC above is deliberate: it leaves more room under the container limit for the collector's own concurrent-processing overhead, on top of the usual non-heap consumers (Metaspace, thread stacks, direct buffers). Validate the choice with a load test against your actual container limit — don't carry either setting over from a bare-metal or VM deployment unchanged.

cgroup v1 vs. v2: Why It Matters for JDK Sizing on OpenShift/Kubernetes

A JVM that reliably detects the container's memory limit under cgroup v1 is not guaranteed to detect it correctly the moment the underlying node moves to cgroup v2 (the default on RHEL 9-based OpenShift 4.13+ nodes and recent Kubernetes distributions) — v2 unifies the resource hierarchy and reports limits through different files (memory.max instead of v1's memory.limit_in_bytes). An older or container-support-immature JDK build that doesn't parse the v2 layout falls back to reporting the host's total memory instead of the pod's limit — which silently defeats -XX:MaxRAMPercentage sizing and leads to an OOM-kill under load that looks, from the JVM's side, like everything was configured correctly.

Stay on a current JDK 17/21 update (container/cgroup v2 support has been solid for several update releases now) and verify detection directly rather than assuming it — the mismatch is silent until the pod gets killed:

# Inside the running container/pod
cat /sys/fs/cgroup/memory.max          # confirms cgroup v2 is in effect and shows the real limit
$JAVA_HOME/bin/java -XX:+PrintFlagsFinal -version | grep -i MaxHeapSize

A full walkthrough of cgroup v1-to-v2 migration pitfalls for containerized JVMs is planned as a dedicated follow-up article on this site — this section intentionally stays at the level of "know to check this" rather than a full diagnostic guide.

Practical Recommendations for Upgrades and Migrations

Upgrading your application server or underlying JDK requires a structured strategy to ensure stability, maintain security compliance, and optimize performance. Consider the following recommendations for your upgrade path:

  • Standardize on LTS JDK Releases: Always target the latest Long-Term Support (LTS) JDK supported by your server generation (e.g., Java 17 for WF 28–31/EAP 8.0; Java 21 for WF 32–39/EAP 8.1; Java 25 for WF 40+). LTS releases benefit from extended security patching and garbage collection optimizations.
  • Execute Comprehensive Integration Testing: Validate application compatibility using integration test suites and TCK profiles. Leverage automation utilities such as the Red Hat Migration Toolkit for Applications (MTA), the Server Migration Tool (for EAP 7 to EAP 8 upgrades), or WildFly Prospero to streamline server configuration updates.
  • Adopt Staged Rollouts and Backups: Deploy upgrades incrementally across Staging/QA environments before pushing to Production. Always maintain snapshot backups of server configuration files (standalone.xml, domain.xml) and database state to support rapid rollback if unexpected runtime issues arise.
  • Understand Runtime Compatibility: WildFly and JBoss EAP execute as standard JVM classpath applications on certified distributions (Red Hat OpenJDK, Oracle JDK, Eclipse Adoptium, Azul Zulu). Note that GraalVM native-image is not a standard runtime target for traditional WildFly or JBoss EAP application deployments.
  • Monitor JVM Performance Metrics Post-Migration: Following a Java runtime upgrade, closely monitor memory utilization, garbage collection pause times, and thread states to fine-tune your JAVA_OPTS parameters for production workloads.

Disclaimer

This website is independently operated and is not affiliated with or maintained by Red Hat. The views expressed here are solely those of the author at the time of writing. It is strongly recommended to consult Red Hat’s official website or seek professional guidance from Red Hat or certified professionals before implementing JBoss EAP in production systems. The author does not assume responsibility for decisions or actions taken based on the information provided on this website.


Recommended Articles

JBoss Tutorial for Beginners: JBoss EAP and WildFly quickstart

Discover the basics of JBoss EAP and WildFly in this comprehensive guide. Learn about Jakarta EE compatibility, modular architecture, and more.

Windup: Migrate Your JBoss EAP 7 Application to JBoss EAP 8 with Detailed Reports and Migration Steps

Learn how to use Windup for a migration report from JBoss EAP 7 to JBoss EAP 8. #Java #Middleware #CloudNative

Deploying Java Web Start Applications on WildFly/JBoss EAP 8

Learn how to deploy Java Web Start applications on WildFly/JBoss EAP 8 with this step-by-step guide.

WildFly vs JBoss EAP: Comprehensive Comparison and Support Policies

Discover the key differences between WildFly and JBoss EAP, including support policies and version updates. #WildFly #JBossEAP #EnterpriseJava