Java Performance Tips
Practical performance tips for Java — measure first, avoid premature optimization, common micro-optimizations.
Java Performance Tips
Java is fast enough for almost everything, but slow code still happens — usually from doing too much work, allocating too much memory, or picking the wrong data structure. The most important performance tip is not a trick at all: measure before you change anything. The tips below show where real time goes and how to avoid the common mistakes.
Measure first, optimize second
Guessing about performance is how you spend hours speeding up code that was never slow. Profile a real workload, find the hot path, and only then optimize it. For quick experiments, System.nanoTime() gives a wall-clock delta; for serious benchmarks use a tool like JMH, which warms up the JIT and accounts for noise.
long start = System.nanoTime();
doWork();
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
System.out.println("Took " + elapsedMs + " ms");Two rules go with this. First, avoid premature optimization — clear code that is fast enough beats clever code that is hard to read. Second, the JVM's JIT compiler optimizes hot code at runtime, so a method only reaches full speed after it has run many times; a single timed call tells you little.
Build strings with StringBuilder
String is immutable, so s += x inside a loop creates a brand-new string and copies every previous character on each iteration — that is O(n²) work. StringBuilder keeps a growable buffer and appends in place, turning the same job into O(n).
// Slow: a new String allocated every iteration
String csv = "";
for (String field : fields) {
csv += field + ",";
}
// Fast: one buffer, appended in place
StringBuilder sb = new StringBuilder();
for (String field : fields) {
sb.append(field).append(',');
}
String csv2 = sb.toString();A single a + b + c outside a loop is fine — the compiler already turns it into one StringBuilder. The problem is concatenation inside a loop.
Pick the right data structure
The biggest wins usually come from algorithmic choices, not micro-tweaks. Looking something up in an ArrayList scans every element (O(n)); a HashMap or HashSet does it in roughly constant time (O(1)). Choose the collection that matches how you actually access the data.
| Need | Use | Lookup cost |
|---|---|---|
| Index access, append at end | ArrayList | O(1) by index, O(n) by value |
| Key/value lookup | HashMap | O(1) average |
| Membership test, no duplicates | HashSet | O(1) average |
| Sorted keys | TreeMap | O(log n) |
| Frequent insert/remove at ends | ArrayDeque | O(1) at ends |
If you know the final size, pass it to the constructor: new ArrayList<>(10_000) or new HashMap<>(capacity). This avoids the repeated reallocation and copying that happens as a collection grows.
Avoid needless object creation
Every object allocated must later be collected, and garbage collection is not free. Reuse immutable values, prefer primitives over their boxed wrappers in tight loops, and don't create objects you immediately throw away.
// Autoboxing: every += boxes a new Integer
Long total = 0L;
for (int i = 0; i < n; i++) total += i; // slow, allocates boxes
// Primitive: no allocation at all
long sum = 0L;
for (int i = 0; i < n; i++) sum += i; // fastOther easy wins: cache compiled Pattern objects instead of calling String.matches() in a loop, reuse a DateTimeFormatter (it's thread-safe and immutable), and favor enhanced for over streams in the hottest inner loops where allocation matters.
What to take from the run:
Same result? trueprovesStringBuilderproduces exactly the same string as+=, so swapping it in changes only speed, never correctness.- The printed "x slower" ratio shows loop concatenation costs many times more than appending to a buffer, because each
+=copies the whole string so far. Both lists size 100000: trueconfirms a pre-sizedArrayListends up identical to a grown one — the constructor hint affects allocation, not contents.Pre-sized faster? trueshows that tellingArrayListits capacity up front avoids the repeated resize-and-copy steps.Map lookups found: 50000 in ... msdemonstrates that 50,000HashMaplookups finish in roughly a millisecond, the payoff of choosing O(1) access over an O(n) list scan.
Practice
Why is using '+=' to build a String inside a loop slow compared to StringBuilder?