HN
Today

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.

7
Score
2
Comments
#11
Highest Rank
6h
on Front Page
First Seen
Mar 20, 2:00 PM
Last Seen
Mar 20, 8:00 PM
Rank Over Time
112913151816

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 immutable String objects create O(n²) copies; StringBuilder is 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 merge or groupingBy is preferred.
  • String.format() in Hot Paths: String.format() is significantly slower due to parsing the format string and complex internal machinery; direct concatenation or StringBuilder is faster for hot paths.
  • Autoboxing in Hot Paths: Using Integer, Long, or Double as 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-catch for routine validation or non-exceptional conditions is costly, as fillInStackTrace() involves an expensive stack walk; explicit pre-validation is far more efficient.
  • Too-Broad Synchronization: Applying synchronized to entire methods or using Collections.synchronizedMap() can turn the lock into a bottleneck under high concurrency; ConcurrentHashMap with LongAdder offers better scalability.
  • Repeated Creation of "Reusable" Objects: Objects like ObjectMapper or DateTimeFormatter are 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 synchronized blocks with virtual threads can pin carrier threads, hindering scalability; ReentrantLock allows virtual threads to unmount (resolved in JDK 24+ for synchronized).

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.