Java Is Fast. Your Code Might Not Be
This post dissects eight common Java performance anti-patterns that silently cripple application speed and consume excessive resources, often slipping past code reviews. The author demonstrates how fixing these seemingly minor issues transformed an order processing app, yielding a 5x throughput increase and 87% less heap usage. Hacker News readers appreciate this practical, data-driven approach to optimizing critical code paths, making it a valuable guide for improving Java application efficiency.
The Lowdown
Jonathan Vogel shares insights from optimizing a Java order-processing application, revealing how common coding patterns can lead to significant performance bottlenecks. Through a series of targeted fixes, he achieved remarkable improvements: a 5x increase in throughput, 87% less heap usage, and 79% fewer garbage collection pauses, all without architectural changes. This article, the first in a three-part series, details eight such anti-patterns that frequently appear in real-world codebases.
- String Concatenation in Loops: Repeated
+operations with immutableStringobjects create O(n²) copies;StringBuilderis the correct, efficient solution. - Accidental O(n²) with Streams Inside Loops: Iterating over an entire collection inside a loop to count related elements leads to quadratic complexity; a single-pass
mergeorgroupingByis preferred. - String.format() in Hot Paths:
String.format()is significantly slower due to parsing the format string and complex internal machinery; direct concatenation orStringBuilderis faster for hot paths. - Autoboxing in Hot Paths: Using
Integer,Long, orDoubleas loop variables or accumulators creates millions of temporary wrapper objects, leading to excessive heap churn; use primitive types instead. - Exceptions for Control Flow: Using
try-catchfor routine validation or non-exceptional conditions is costly, asfillInStackTrace()involves an expensive stack walk; explicit pre-validation is far more efficient. - Too-Broad Synchronization: Applying
synchronizedto entire methods or usingCollections.synchronizedMap()can turn the lock into a bottleneck under high concurrency;ConcurrentHashMapwithLongAdderoffers better scalability. - Repeated Creation of "Reusable" Objects: Objects like
ObjectMapperorDateTimeFormatterare expensive to construct due to setup work; they should be instantiated once and reused as static final fields. - Virtual Thread Pinning (JDK 21-23): Blocking I/O inside
synchronizedblocks with virtual threads can pin carrier threads, hindering scalability;ReentrantLockallows virtual threads to unmount (resolved in JDK 24+ forsynchronized).
Vogel emphasizes that while individual anti-patterns might seem innocuous, their cumulative effect in hot paths can drastically degrade performance, increase resource consumption, and impact scalability across a fleet of production servers. The series promises to further explore profiling data and automation for identifying these issues.