Tag Archives: Performance

Performance — performance optimization, benchmarking, and profiling in Java applications

10 AI Prompts to Review and Improve Java Code Quality

Code review is the most effective quality gate in software development — but it is also the most time-constrained. Reviewers scan for obvious bugs and style issues, but subtle problems like carrier pinning inside synchronized blocks, hidden N+1 queries in service layers, and resource leaks in non-obvious code paths regularly slip through. I built these prompts after a quarter where our team’s post-incident reviews kept surfacing the same categories of bug: thread-safety assumptions broken by virtual threads, transaction boundaries that worked in tests but silently failed under concurrent load, and SOLID violations that only became painful when the code needed to be extended. The pattern was clear — these were not one-off mistakes, they were the class of problem that human reviewers consistently miss because they require holding the entire call graph in working memory at once. AI assistants handle this better. These 10 prompts are structured to extract that analysis reliably.

This post gives you 10 AI prompts engineered for Java code quality review. Each prompt targets a specific class of problem, provides a structured diagnosis checklist, and requests actionable fixes rather than just observations. Use them before a pull request, as part of a legacy codebase audit, or as a learning tool when onboarding to an unfamiliar module.

For complementary content, see 10 AI Prompts to Generate JUnit 6 Tests and Virtual Threads vs Platform Threads — Benchmarks and Code.

Continue reading 10 AI Prompts to Review and Improve Java Code Quality

Java 21 to Java 25 LTS: Every Feature You Actually Need to Know

Java 21 was the biggest LTS since Java 8, and Java 25 (LTS, September 2025) finalizes most of the preview features that shipped alongside it. If you last touched modern Java during the Java 17 era, the jump to 25 is not just a syntax refresh — it fundamentally changes how you write concurrent code, pattern-match data, and model domain objects. This post walks through the six features that matter most for day-to-day Java work, with runnable code for each.

Continue reading Java 21 to Java 25 LTS: Every Feature You Actually Need to Know

28× Faster? Virtual Threads vs Platform Threads in Java: Real Benchmarks, Code, and When to Use Which

Virtual threads shipped as a final feature in Java 21 and matured further in Java 25 (JEP 491 eliminates carrier-thread pinning inside synchronized blocks). The marketing says “millions of threads, almost free” — but what does that actually look like under a stopwatch? In this post we run reproducible benchmarks against platform threads, show where virtual threads win big (and where they don’t), and give you a clear decision table for your own code.

Continue reading 28× Faster? Virtual Threads vs Platform Threads in Java: Real Benchmarks, Code, and When to Use Which

Java Streams API Deep Dive + Collectors Cookbook

The Stream API turned ten years old with Java 18, and the Collectors utility class has quietly grown into one of the most powerful data-transformation toolkits in the JDK. This post is a working cookbook: the patterns you actually reach for in production code, each with a runnable snippet and an explanation of why it works. A set of AI prompts at the end will help you refactor imperative loops into clean pipelines automatically.

Continue reading Java Streams API Deep Dive + Collectors Cookbook

Java Concurrency Deep Dive: CompletableFuture, ExecutorService, ForkJoinPool with Examples

Even in the age of virtual threads, the classic Java concurrency toolbox — ExecutorService, CompletableFuture, and ForkJoinPool — is not going anywhere. Each one solves a different problem, and picking the wrong one is a common source of latency bugs. This post walks through all three with runnable examples, then adds AI prompts to review and refactor your own concurrent code.

Continue reading Java Concurrency Deep Dive: CompletableFuture, ExecutorService, ForkJoinPool with Examples

Performance Testing Using JUnit 6 (Benchmarks & Techniques)

JUnit 6 is excellent for verifying correctness, but measuring how fast your code runs requires a different approach. A naive long start = System.currentTimeMillis() before and after a method call is not a reliable benchmark — JVM warm-up, JIT compilation, and garbage collection all introduce noise that makes single-run timing meaningless. This guide covers professional performance testing techniques for Java: from JUnit 6’s @Timeout for basic bounds, to full JMH micro-benchmarks integrated into a Maven build.

Three Levels of Performance Testing

LevelToolPurposeAccuracy
Basic boundsJUnit 6 @TimeoutFail if a test exceeds a time limitLow — single cold run
Regression detectionJUnit 6 + assertTimeoutCatch performance regressions in CIMedium
Micro-benchmarkingJMH (Java Microbenchmark Harness)Precise throughput and latency measurementHigh — warmed JVM, statistics

Level 1: @Timeout — Simple Time Bounds

Use @Timeout to fail a test if it takes too long. This catches catastrophic performance regressions (infinite loops, accidental N+1 queries, missing index):

import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;

class OrderSearchPerformanceTest {

    // @Timeout: test fails if it takes longer than the specified duration
    // Applied at method level
    @Test
    @Timeout(value = 2, unit = TimeUnit.SECONDS)
    @Tag("performance")
    @DisplayName("Searching orders by customer email returns within 2 seconds")
    void searchingOrdersByEmailReturnsWithinTwoSeconds() {
        List<Order> results = orderRepository.findByEmail("[email protected]");
        assertNotNull(results);
        // If this takes >2s, test fails with:
        // org.opentest4j.AssertionFailedError: method timed out after 2 seconds
    }

    // Apply @Timeout at class level to set a default for ALL tests in the class
    @Timeout(5) // 5 seconds default for every test
    class AllSearchesMustBeFast {

        @Test
        void searchByEmailIsWithin5Seconds() { /* ... */ }

        @Test
        @Timeout(1) // override: this specific test must complete in 1 second
        void searchByIdIsWithin1Second() { /* ... */ }
    }
}

// Global timeout: set in junit-platform.properties
// junit.jupiter.execution.timeout.default=30s  (safety net for all tests)
Continue reading Performance Testing Using JUnit 6 (Benchmarks & Techniques)

Mutation Testing with PIT and JUnit 6 (Improve Test Quality)

Code coverage tells you which lines your tests execute. It does not tell you whether your tests would catch a bug if one was introduced. Mutation testing answers the harder question: if I intentionally break the code in small ways, do my tests detect it? PIT (Pitest) is the leading mutation testing tool for Java, and it integrates directly with JUnit 6. This guide shows you how to set it up, read the reports, and use the results to genuinely improve your test suite quality.

What Is Mutation Testing?

PIT automatically modifies your production code in small ways called mutations — changing a + to a -, a > to a >=, removing a method call, negating a condition. Each modified version of the code is a mutant. PIT then runs your test suite against each mutant:

  • Killed mutant — at least one test failed against this mutant. ✅ Good: your tests caught the bug.
  • Surviving mutant — all tests passed against this mutant. ❌ Bad: your tests did not catch this bug.
  • Mutation score — percentage of killed mutants. Higher is better.

A mutation score of 85% means 85% of introduced bugs would be caught by your test suite. The surviving 15% represent genuine gaps in your tests.

Setup: PIT with Maven

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.16.1</version>
    <dependencies>
        <!-- JUnit 5/6 Platform support for PIT -->
        <dependency>
            <groupId>org.pitest</groupId>
            <artifactId>pitest-junit5-plugin</artifactId>
            <version>1.2.1</version>
        </dependency>
    </dependencies>
    <configuration>
        <!-- Target only your application code, not generated or config classes -->
        <targetClasses>
            <param>com.example.service.*</param>
            <param>com.example.domain.*</param>
        </targetClasses>
        <!-- Only run fast unit tests (tagged 'unit') for mutation analysis -->
        <targetTests>
            <param>com.example.*Test</param>
        </targetTests>
        <!-- Mutation operators to apply -->
        <mutators>
            <mutator>STRONGER</mutator> <!-- recommended set for Java -->
        </mutators>
        <!-- Fail build if mutation score drops below 80% -->
        <mutationThreshold>80</mutationThreshold>
        <!-- Output formats: HTML (human), XML (CI integration) -->
        <outputFormats>
            <outputFormat>HTML</outputFormat>
            <outputFormat>XML</outputFormat>
        </outputFormats>
        <!-- Verbose: show each mutant result -->
        <verbose>false</verbose>
    </configuration>
</plugin>

Running PIT

# Run mutation tests and generate report
mvn test-compile org.pitest:pitest-maven:mutationCoverage

# Or bind to verify phase: mvn verify
# Report generated at: target/pit-reports/YYYYMMDDHHMI/index.html
Continue reading Mutation Testing with PIT and JUnit 6 (Improve Test Quality)