Performance

26 posts in this section

Container Reuse for Fast Feedback Loops

The singleton pattern shares a container within a single test run. Container reuse goes further — it keeps the container alive after the JVM exits and reuses it in the next test run. The first run pays the startup cost (8 seconds for Kafka). Every subsequent run skips it entirely. For a developer who runs the test suite dozens of times per day, this saves minutes of waiting. What You’ll Learn Enabling container reuse with .

Continue reading »

Parallel Test Execution with Testcontainers

A test suite with 50 integration test classes runs sequentially in 10 minutes. Configured for parallel execution across 4 threads, it completes in 3 minutes. Parallel test execution with Testcontainers requires careful data isolation — tests running simultaneously against the same database will step on each other’s data without it. This article covers JUnit 5 parallel configuration, container sharing strategies, and data isolation techniques. What You’ll Learn JUnit 5 parallel execution configuration with junit-platform.

Continue reading »

Singleton Containers and Shared Base Classes

With 20 integration test classes, each starting its own PostgreSQL container, you pay 20 container starts at 2 seconds each — 40 seconds of pure overhead before a single assertion runs. The singleton pattern starts one container per JVM, shares it across all test classes, and cuts that 40 seconds to 2. This is the most impactful performance optimization for large Testcontainers test suites. What You’ll Learn The singleton pattern using a static initializer block Abstract base class design for sharing containers and @DynamicPropertySource Why you must not combine @Testcontainers/@Container with the singleton pattern Spring TestContext caching — how it works and how to maximize reuse The @ImportTestcontainers approach for Spring Boot 3.

Continue reading »

AOT Compilation in Java 25 (JEP 514 & 515): Faster Startup, Zero Warm-Up

The Java Startup Problem Java’s performance story has always had one weak spot: startup time. When a JVM starts, it: Loads and verifies class bytecode Interprets bytecode (slow) Profiles which methods are called most (warm-up) Compiles hot methods to native code via JIT (takes time and CPU) Eventually reaches peak throughput This process takes seconds for large applications. For a Spring Boot application, typical warm-up to peak throughput can take 10–30 seconds.

Continue reading »

Compact Object Headers (JEP 519): 33% Less Heap Overhead

What Is an Object Header? Every single Java object — every String, every Integer, every record, every array — carries a header that the JVM uses for bookkeeping. Your code never sees this header; it lives alongside the object’s fields in memory. Before Java 25, on a 64-bit JVM, the header occupied 96 to 128 bits (12–16 bytes): ┌─────────────────────────────────────────────────────────┐ │ Mark Word (64 bits) │ │ ─ identity hash code │ │ ─ lock state (biased lock / thin lock / fat lock) │ │ ─ GC age bits │ ├─────────────────────────────────────────────────────────┤ │ Class Pointer (32 bits compressed / 64 bits full) │ │ ─ pointer to the object's class (Klass* in HotSpot) │ └─────────────────────────────────────────────────────────┘ With UseCompressedOops (default), the class pointer is compressed to 32 bits, giving a 96-bit (12-byte) header.

Continue reading »

Generational Shenandoah (JEP 516): Best GC for Low Latency

GC Background: Why Generations Matter The Generational Hypothesis is the foundation of most modern GC design: Most objects die young. In a typical Java application, the vast majority of objects are short-lived: request/response objects, DTOs, builder instances, stream pipeline intermediates. They are created, used briefly, and then immediately eligible for collection. A GC that knows about this pattern can be far more efficient than one that treats all objects equally:

Continue reading »

Garbage Collection: G1GC, ZGC, Epsilon, and AppCDS

GC Changes Across Java 9–11 Release Change JEP Java 9 G1GC becomes the default GC JEP 248 Java 9 Unified GC logging (-Xlog:gc*) JEP 271 Java 10 Parallel Full GC for G1 JEP 307 Java 10 Application Class-Data Sharing (AppCDS) JEP 310 Java 11 Epsilon: No-Op GC JEP 318 Java 11 ZGC: Scalable Low-Latency GC (experimental) JEP 333 G1GC as Default (JEP 248, Java 9) G1 (Garbage-First) replaced Parallel GC as the default on systems with ≥2 CPUs and ≥2 GB heap.

Continue reading »

Java 11 Production Checklist and Performance Best Practices

Production Readiness Checklist [ ] JDK distribution chosen and version pinned [ ] Heap and Metaspace sized correctly [ ] GC selected and tuned for your workload [ ] Container-aware JVM flags set [ ] AppCDS archive built for faster startup [ ] JFR always-on recording configured [ ] GC logging enabled with rotation [ ] Security-related algorithms locked down [ ] Thread and connection pool sizes verified [ ] JVM exit flags prevent silent crashes Baseline JVM Flags for Java 11 Start with these flags and tune from here:

Continue reading »

Java 17 Production Checklist and Performance Best Practices

Production Baseline JVM Flags Start every Java 17 production deployment with this baseline: java \ # GC — choose one (see GC section) -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ \ # Heap sizing -Xms4g -Xmx4g \ \ # GC logging — essential for diagnosis -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \ \ # OOM diagnostics -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/app/heap-dump.hprof \ -XX:+ExitOnOutOfMemoryError \ \ # Metaspace -XX:MaxMetaspaceSize=512m \ \ # Code cache -XX:ReservedCodeCacheSize=512m \ \ # JFR — always-on profiling -XX:StartFlightRecording=duration=0,filename=/var/log/app/profile.

Continue reading »

Java 21 Production Checklist and Performance Best Practices

The Production Mindset Migrating to Java 21 unlocks new capabilities, but production readiness requires deliberate configuration. The JVM defaults are conservative — designed to work reasonably across a wide range of workloads, not to be optimal for any specific one. This article covers: Which JVM flags to set for every production Java 21 deployment GC selection and tuning for different workload profiles Virtual thread configuration and monitoring Container-aware JVM settings Observability and profiling Startup and memory optimization JVM Flags: The Production Baseline Start every Java 21 production deployment with this baseline flag set:

Continue reading »