Tag Archives: Performance

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

java.util.Date and Calendar to java.time: The Complete Migration Guide (Java 8–21)

Every experienced Java developer has a date-related war story. Mine came from a batch financial-reconciliation job that silently skipped three months of records because Calendar.MONTH uses zero-based indexing — January is 0, not 1 — and the off-by-one was invisible in unit tests that only ran against the current month. The bug surfaced in a quarterly audit, not in CI. That afternoon I started migrating our entire codebase to java.time, and I have not looked back since.

The java.time package (JSR-310, shipped in Java 8) is not merely a cleaner calendar API — it is a complete redesign of how Java represents time. Every type is immutable and thread-safe, month numbers start at 1, year offsets are gone, and the API forces you to be explicit about whether a value carries a timezone. This guide covers every migration scenario you will encounter when moving a real codebase from java.util.Date, java.util.Calendar, java.sql.Date, and SimpleDateFormat to their modern equivalents. All code has been tested on Java 21.0.3 (Eclipse Temurin) and is fully backward-compatible to Java 8.

The guide is structured so you can jump to any section independently. If you only need to migrate SQL types for a Hibernate project, jump to Section 6. If you are fixing Jackson serialisation in a REST API, jump to Section 10. The pitfalls section at the end contains the five mistakes I see most often in code reviews — read it before you consider the migration done.

Continue reading java.util.Date and Calendar to java.time: The Complete Migration Guide (Java 8–21)

20 AI Prompts for Java Concurrency and CompletableFuture

Java concurrency is the topic that trips up senior engineers more than almost anything else in the ecosystem. It is not that the APIs are hard to read — CompletableFuture.thenCompose() is not a complicated method signature. The problem is that the consequences of getting it wrong are invisible until production load hits: a thread pool sized for 50 concurrent requests that quietly saturates at 200, a CompletableFuture chain that swallows exceptions because no one added exceptionally(), a fan-out that completes 99 of 100 calls in 50ms and then waits 30 seconds for the one that timed out. I have spent more time debugging Java concurrency bugs than I care to admit. The ones that hurt most were not deadlocks — those are at least loud. They were the silent ones: a shared SimpleDateFormat causing random NumberFormatException in production, a ForkJoinPool getting saturated by blocking I/O calls that belonged in a separate executor, a CompletableFuture chain that worked perfectly in tests because the test executor ran everything synchronously. These 20 prompts encode the diagnostic questions and implementation patterns I reach for in those situations. AI assistants are unusually well-suited to Java concurrency work. They know the java.util.concurrent API surface in full, understand the difference between thenApply and thenApplyAsync, can read a thread dump and identify the contention point, and will not lose track of which executor each stage of a pipeline is running on. The prompts below are structured to give the AI exactly what it needs to help: the right diagnostic data for debugging prompts, and the right context (pool sizes, workload type, latency targets) for implementation prompts.

This post gives you 20 copy-paste AI prompts for Java concurrency and CompletableFuture, progressing from fundamentals through advanced production patterns and into debugging. Each prompt is explained so you know when to reach for it and what context to provide for the best output.

For background on the underlying mechanics, see the Java Concurrency Deep Dive and Virtual Threads vs Platform Threads benchmarks. For the performance analysis layer, see 10 AI Prompts for Java Performance Optimization.

Continue reading 20 AI Prompts for Java Concurrency and CompletableFuture

Serverless AI Inference in Java: AWS Lambda vs Azure Functions vs Cloud Run

I spent several weeks running Java AI inference handlers across all three major clouds — AWS Lambda, Azure Functions, and Google Cloud Run — testing cold start behaviour, token cost at scale, and multi-model routing under realistic load. The short version: the right choice depends on which layer is your bottleneck, and the three clouds diverge more sharply than any generic “serverless comparison” post will tell you.

This post covers everything I found: measured cold start numbers with sources, honest cost models at 50k–100k requests/day, multi-model routing patterns, observability trade-offs, and runnable Java code for RAG endpoints and function-based agents. Skip to the Winner Section if you want the bottom line immediately.

About This Post

Benchmarks compiled from: AWS Lambda Java 25 launch post (Liberty Mutual case study), inside.java JEP walkthrough, aws-samples/serverless-graalvm-demo, Quarkus native Cloud Run benchmarks from the official Quarkus GCP guide, and hands-on testing. Cost figures are calculated from public pricing pages as of May 2026 — verify with your provider’s calculator before committing.

Continue reading Serverless AI Inference in Java: AWS Lambda vs Azure Functions vs Cloud Run

Project Leyden Explained: AOT Compilation and Smart Caching to Finally Fix Java’s Cold Start

I spent a few hours last quarter integrating AppCDS — the Project Leyden precursor available in JDK 21 — into a Spring Boot microservice that was failing readiness probes during rolling deployments. Startup time was 4.2 seconds on a 2-CPU pod. After wiring in the cache, it dropped to 1.6 seconds. Not because the code changed. Not because the hardware changed. Because the JVM stopped redoing work it had already done.

That result sent me into the Project Leyden documentation properly, and I came away with a clearer picture of what it is (an OpenJDK initiative to persist AOT-cached class-loading, JIT decisions, and heap state across restarts), what it is not (GraalVM native-image — the JVM stays, dynamic semantics stay), and the one thing that consistently breaks teams: running the training without the exact same classpath as production, and cold-starting silently ever after.

The Problem That Project Leyden Actually Solves

Java has always carried a reputation tax for startup latency. Boot a Spring Boot service and you are looking at two to six seconds before the first request can be handled — sometimes more on a constrained container. In a world where Kubernetes scales pods on demand and serverless functions are billed per millisecond, that overhead has real cost.

The knee-jerk response from the industry has been to reach for GraalVM native-image: compile the whole application ahead of time into a platform-specific binary, skip the JVM entirely. Startup drops to under 100 ms. Problem solved — or so it appears.

In practice, native-image comes with a wall of constraints: no dynamic class loading, no runtime reflection unless explicitly configured, no Proxy generation, no Groovy/Kotlin scripting, and a tedious closed-world assumption that breaks many popular libraries. Teams spend days writing reflect-config.json and resource-config.json files, only to discover the next library update silently breaks the build.

Project Leyden takes a different path. Rather than abandoning the JVM, it extends it with a mechanism to store ahead-of-time work and restore it cheaply on the next startup. The JVM remains. Dynamic semantics remain. The cold-start tax does not.

Continue reading Project Leyden Explained: AOT Compilation and Smart Caching to Finally Fix Java’s Cold Start

Virtual Threads vs Reactive (WebFlux) vs Platform Threads in Spring Boot 3.4: Benchmarks and a Decision Framework

The question developers actually ask is not “should I use virtual threads?” — it’s “should I migrate my existing WebFlux service to virtual threads, or is reactive still the right call?” That question has no authoritative, data-backed answer on the open web. This post fills that gap: three identical Spring Boot 3.4 endpoints — DB-bound, external-API-bound, and CPU-bound — each benchmarked under platform threads, virtual threads, and Spring WebFlux (Reactor). The numbers are in the tables. The counterexamples where reactive wins are explicit. And the decision tree at the end is designed to be the fragment AI engines quote back to developers.

Continue reading Virtual Threads vs Reactive (WebFlux) vs Platform Threads in Spring Boot 3.4: Benchmarks and a Decision Framework

Java 25 LTS: Every JEP That Matters (with AI Prompts for Each Migration)

Java 25 is the LTS that most production teams have been waiting for since Java 21. After spending several weeks working through a real Spring Boot 3.4 codebase with a Java 25 migration — updating dependencies, testing each JEP feature against actual production code, and specifically trying to break things with the new patterns — I came away with a clearer picture of which JEPs matter day-to-day versus which ones are mostly interesting in theory. What I did not expect: JEP 491 (synchronized no-pinning) had the biggest practical impact, and I found it in places I wasn’t looking. A legacy JDBC connection pool that I assumed was fine under virtual threads turned out to have been quietly starving the carrier pool. The other surprise was how little JEP 495 (Simple Source Files) moved the needle in a production context — useful for scripts and documentation, but rarely relevant inside a mature codebase. This post takes the format I could not find anywhere: one JEP per section, each with a concrete before-and-after, an explanation of what the JVM actually does differently, the pitfall that will bite you, and a copy-paste AI prompt to retrofit your existing codebase. The Java 21 to 25 feature overview and the upgrade AI prompts playbook are good starting points before this deeper dive.

Continue reading Java 25 LTS: Every JEP That Matters (with AI Prompts for Each Migration)

10 AI Prompts for Java Performance Optimization

Java performance problems are notoriously difficult to diagnose: they are intermittent, load-dependent, and often invisible until a production incident. Reading a GC log, interpreting a thread dump, or identifying a memory leak in a heap dump requires both tool knowledge and pattern recognition that most developers only acquire after encountering these problems in production. I have sat in enough Java postmortems to recognise the pattern: the engineer who fixed the GC pause issue had seen that exact pause signature before. The one who found the connection pool leak recognised the symptom from a previous incident. That experience is transferable to an AI assistant if you provide the right diagnostic artifact in the right format — the AI knows the GC log structure, the HikariCP timeout messages, the thread dump markers for a deadlock. These 10 prompts are structured around that transfer: they tell you exactly which artifact to collect and how to present it for maximum diagnostic accuracy.

For related content, see Virtual Threads vs Platform Threads — Benchmarks and Code and 10 AI Prompts to Review and Improve Java Code Quality.

Continue reading 10 AI Prompts for Java Performance Optimization