“Optimize your Java code” is often vague advice. Here are specific, concrete techniques that tend to have real, measurable impact — along with the reasoning for why each one matters, so you can judge when it actually applies to your situation.
Profile before you optimize
Tip: never optimize based on a guess. A profiler (like async-profiler, VisualVM, or your APM tool) will often surprise you about where time is actually being spent.
Developers frequently optimize the wrong thing because it “feels slow” rather than measuring where time actually goes. A 2-hour profiling session before touching code usually saves far more time than it costs.
String concatenation in loops
// Slow: creates a new String object on every iteration
String result = "";
for (String item : items) {
result += item + ", ";
}
Each += on a String creates an entirely new object, because String is immutable in Java. In a loop, this becomes quadratic behavior as the string grows.
// Fast: StringBuilder mutates one buffer instead of allocating repeatedly
StringBuilder result = new StringBuilder();
for (String item : items) {
result.append(item).append(", ");
}
StringBuilder avoids the repeated allocation entirely, making this linear instead of quadratic for large inputs.
Choosing the right collection
Using the wrong collection type for the access pattern is a common, easy-to-fix bottleneck:
| Access pattern | Use |
|---|---|
| Frequent lookups by key | HashMap |
| Frequent lookups by key, need insertion order | LinkedHashMap |
| Frequent contains()/add() on a set | HashSet |
| Frequent index-based access | ArrayList |
| Frequent insertion/removal at both ends | ArrayDeque |
A common real mistake: using ArrayList.contains() in a hot loop, which is O(n) per call, when a HashSet would make the same check O(1).
JVM heap sizing
Setting -Xmx (max heap) too small causes excessive, expensive garbage collection cycles; setting it far larger than needed can increase GC pause times when collections do happen (more memory to scan). A reasonable starting point for many workloads is sizing the heap to comfortably fit your application’s actual working set, then tuning based on GC logs (-Xlog:gc) rather than guessing.
java -Xms512m -Xmx512m -Xlog:gc -jar app.jar
Setting -Xms (initial heap) equal to -Xmx avoids the overhead of the heap resizing itself during warmup.
Avoid unnecessary object creation in hot paths
Autoboxing (converting int to Integer, etc.) inside tight loops creates unnecessary object churn. Where performance matters in a hot path, prefer primitive arrays and primitive-typed collections over their boxed equivalents.
What’s next
Performance work pays off most when it’s guided by actual measurement — pick the technique that matches a bottleneck you’ve confirmed, not one you’re guessing about.