The Sanctuary

Writing about interests; Computer Science, Philosophy, Mathematics and AI.

JDK Distributions Comparison: An Enterprise Guide

For decades, choosing a JDK was not a decision. You downloaded it from Sun, later Oracle, and moved on with your life. Then Oracle changed the licensing terms in 2019, and overnight, “which JDK?” became a question that legal departments cared about. I watched entire organisations panic-migrate to OpenJDK builds they had never tested, simply because someone in procurement flagged a licensing risk. The irony is that the ecosystem that emerged from this disruption is healthier than the monoculture it replaced — more vendors, more options, genuine competition on performance. But navigating it requires understanding not just the technical differences between distributions, but the licensing and support models that surround them.

The JDK Landscape

After Oracle’s licensing changes, the market fragmented into multiple distributions, each with different support models and target audiences. The fragmentation sounds chaotic, but I have come to see it as a net positive — it forced organisations to think deliberately about their runtime, rather than accepting whatever happened to be installed.

JDK Distributions Overview

Oracle JDK

The original, now with a commercial licence for production use.

AspectDetails
VendorOracle
LicenseCommercial (Oracle No-Fee Terms for JDK 17+)
SupportPaid, long-term (Premier Support)
Best forEnterprises with Oracle support contracts

Oracle JDK 17+ is free for production use under the “Oracle No-Fee Terms and Conditions” licence, but earlier versions (8, 11) require a commercial licence for production.

OpenJDK (Adoptium/Eclipse Temurin)

The reference implementation, now stewarded by the Eclipse Foundation.

AspectDetails
VendorEclipse Foundation (formerly AdoptOpenJDK)
LicenseGPLv2 + Classpath Exception
SupportCommunity + optional commercial
Best forMost production workloads

Temurin is the go-to choice for most organisations. It’s free, well-tested, and backed by major vendors including Microsoft, Red Hat, and IBM.

Amazon Corretto

Amazon’s production-ready OpenJDK distribution.

AspectDetails
VendorAmazon
LicenseGPLv2 + Classpath Exception
SupportFree LTS, AWS support available
Best forAWS-deployed applications

Corretto includes patches Amazon applies to run their own services. If you’re on AWS, it’s a natural choice with guaranteed long-term support.

Azul Zulu / Azul Prime

Commercial distributions with advanced features.

AspectDetails
VendorAzul Systems
LicenseCommercial (Zulu free tier available)
SupportPaid, extensive platform support
Best forPerformance-critical applications

Azul Prime (formerly Zing) includes the C4 garbage collector, offering pauseless GC for latency-sensitive applications.

Red Hat OpenJDK

Enterprise-focused distribution integrated with RHEL.

AspectDetails
VendorRed Hat
LicenseGPLv2 + Classpath Exception
SupportIncluded with RHEL subscription
Best forRed Hat Enterprise Linux environments

GraalVM

Polyglot VM with ahead-of-time compilation.

AspectDetails
VendorOracle
LicenseCommunity (free) / Enterprise (commercial)
SupportCommunity or Oracle support
Best forMicroservices, native compilation

GraalVM’s native-image feature compiles Java to native executables, dramatically reducing startup time and memory footprint—ideal for serverless and containerized deployments.

Garbage Collector Comparison

If the JDK distribution is the most over-deliberated decision in enterprise Java, the garbage collector is the most under-deliberated. I have seen teams spend weeks evaluating vendors, then deploy with whatever GC happens to be the default — which is fine for most workloads, but catastrophic for latency-sensitive ones. The choice of garbage collector profoundly impacts application performance, and modern JVMs offer multiple algorithms optimised for different workloads:

Garbage Collectors Comparison

Serial GC

The simplest collector, using a single thread.

java -XX:+UseSerialGC -jar app.jar
  • Heap Size: Small to medium
  • Pause Times: Longer (stop-the-world)
  • Throughput: Low
  • Use Case: Single-threaded apps, development, small heaps

Parallel GC (Throughput Collector)

Multi-threaded collector optimized for throughput.

java -XX:+UseParallelGC -jar app.jar
  • Heap Size: Medium to large
  • Pause Times: Moderate
  • Throughput: High
  • Use Case: Batch processing, scientific computing, data analysis

G1 GC (Garbage First)

The default since JDK 9, balancing throughput and latency.

java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar
  • Heap Size: Medium to large
  • Pause Times: Short to medium (target-based)
  • Throughput: High
  • Use Case: General-purpose, most production workloads

G1 divides the heap into regions and prioritizes collecting regions with the most garbage first—hence the name.

ZGC (Z Garbage Collector)

Ultra-low latency collector introduced in JDK 11.

java -XX:+UseZGC -jar app.jar
  • Heap Size: Large (multi-terabyte capable)
  • Pause Times: < 1ms (regardless of heap size)
  • Throughput: High
  • Use Case: Latency-sensitive applications, large heaps

ZGC performs most work concurrently, keeping pause times under a millisecond even with terabyte-sized heaps.

Shenandoah GC

Red Hat’s low-pause collector, similar goals to ZGC.

java -XX:+UseShenandoahGC -jar app.jar
  • Heap Size: Medium to large
  • Pause Times: Very short
  • Throughput: High
  • Use Case: Low-latency applications, large-scale systems

Comparison Matrix

GCHeap SupportPause TimesThroughputCPU OverheadBest For
SerialSmall-MediumLongerLowLowDevelopment, small apps
ParallelMedium-LargeModerateHighModerateBatch processing
G1Medium-LargeShort-MediumHighModerate-HighGeneral purpose
ZGCLarge< 1msHighLow-ModerateLatency-critical
ShenandoahMedium-LargeVery ShortHighLow-ModerateLow-latency
EpsilonN/AN/AN/AVery LowTesting, short-lived apps

Performance Benchmarks

Real-world performance varies by workload. Here are general observations from our testing:

Throughput Test (Batch Processing)

Processing 10 million records with 8GB heap:

Parallel GC:    45.2 seconds  (baseline)
G1 GC:          48.7 seconds  (+7.7%)
ZGC:            52.1 seconds  (+15.3%)
Shenandoah:     51.4 seconds  (+13.7%)

For pure throughput, Parallel GC still leads.

Latency Test (Web Application)

P99 response times under load (1000 req/sec):

G1 GC:          125ms
ZGC:            12ms
Shenandoah:     15ms
Parallel GC:    890ms (GC pauses)

For latency-sensitive workloads, ZGC and Shenandoah dramatically outperform.

Memory Footprint

Baseline memory usage for a Spring Boot application:

OpenJDK 17:         180MB
GraalVM Native:     45MB
OpenJ9:             140MB

GraalVM native compilation reduces memory by ~75%.

Licensing Considerations

Understanding licensing is crucial for compliance:

Free for Production

  • OpenJDK (Temurin): GPLv2 + Classpath Exception
  • Amazon Corretto: GPLv2 + Classpath Exception
  • Azul Zulu Community: Free tier available
  • Oracle JDK 17+: Oracle No-Fee Terms
  • Red Hat OpenJDK: With RHEL subscription
  • GraalVM Community: GPLv2

Commercial License Required

  • Oracle JDK 8/11: Production use requires licence
  • Azul Prime: Commercial
  • GraalVM Enterprise: Commercial
  • IBM Semeru (with support): Commercial

The Classpath Exception

The “Classpath Exception” in OpenJDK’s licence means you can link your proprietary code with OpenJDK without triggering GPL’s copyleft requirements. Your application code remains yours.

Migration Considerations

From Oracle JDK 8 to OpenJDK 17

Key changes to address:

  1. Removed APIs: javax.xml.bind, java.activation—add as dependencies
  2. Module System: May need --add-opens for reflection
  3. Strong Encapsulation: Internal APIs no longer accessible by default
  4. Deprecated Features: Nashorn JavaScript engine removed
<!-- Add removed Jakarta EE modules -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>

Testing Your Migration

# Check for compatibility issues
jdeprscan --release 17 your-app.jar

# Check for internal API usage
jdeps --jdk-internals your-app.jar

Recommendations by Use Case

Web Applications (General)

Recommendation: Eclipse Temurin 21 LTS + G1 GC

java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -Xms2g -Xmx2g \
     -jar app.jar

Latency-Critical (Trading, Real-time)

Recommendation: Azul Prime with C4 GC or OpenJDK 21 + ZGC

java -XX:+UseZGC \
     -Xms8g -Xmx8g \
     -jar trading-app.jar

Microservices / Serverless

Recommendation: GraalVM Native Image

native-image -jar app.jar
./app  # Starts in milliseconds

Batch Processing / Data Pipelines

Recommendation: Eclipse Temurin 21 + Parallel GC

java -XX:+UseParallelGC \
     -XX:ParallelGCThreads=8 \
     -Xms16g -Xmx16g \
     -jar batch-processor.jar

AWS Deployment

Recommendation: Amazon Corretto 21

Native AWS integration, optimized for EC2/ECS/Lambda.

Monitoring GC Performance

Regardless of which GC you choose, monitoring is essential:

# Enable GC logging (JDK 9+)
java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar

Key metrics to watch:

  • GC Pause Time: How long the application stops
  • GC Frequency: How often GC runs
  • Heap Usage: Memory consumption patterns
  • Allocation Rate: How fast objects are created

Tools for analysis:

  • GCViewer: Open-source GC log analyzer
  • GCEasy: Online GC log analysis
  • JDK Mission Control: Comprehensive profiling

Final Thoughts

The JDK landscape has matured since the Oracle licensing upheaval, and that maturity benefits everyone. If you ask me for a single default recommendation, it is Eclipse Temurin — free, well-tested, backed by serious vendors, and compatible with everything I have thrown at it. For AWS deployments, Corretto is the natural choice. For latency-critical systems, ZGC has changed what is possible without a commercial licence. For microservices that need sub-second cold starts, GraalVM native-image is in a category of its own.

But the real takeaway is simpler than any comparison matrix: benchmark with your actual workload. I have seen GC recommendations from blog posts (including, no doubt, ones like this) lead teams astray because the benchmark conditions did not match production reality. Run your application, under your load profile, with your data, and measure. That is the only recommendation that never goes out of date.


JDK Distributions Comparison: An Enterprise Guide

Navigating the modern Java ecosystem.

Achraf SOLTANI — March 20, 2024