[MM’s] Tiny Bench Notes — Fast or Pretty? The Java Palindrome Benchmarks
7 Different String-Reversal Algorithms Through JMH to Find the Winner

Why Palindromes Are the Perfect Micro-Benchmark
Every engineer has that one tiny utility method they’ve written dozens of times: reverse a string, detect vowels, trim whitespace or check for a palindrome.
It’s deceptively simple work. But these “throwaway” functions often live in performance-sensitive code paths. Over thousands or millions of invocations, the cost adds up.
⚡ TL;DR (Quick Recap)
- Two-pointer is fastest overall, especially for non-palindromes.
- Recursion is unexpectedly strong for short strings.
- Char arrays win for long palindromes (100+ chars).
- StringBuilder is 4–5× slower, but clean and readable.
- Streams and regex preprocessing are the slowest options.
Benchmark Setup
All approaches followed the same rules:
- Case-sensitive
- Whitespace-sensitive
- Null/empty values return false
Test data included:
- Short (5 chars)
- Medium (20 chars)
- Long (100 chars)
Everything was measured using JMH 1.37 on Java 25.
The benchmarks were run on OpenJDK 25 (Temurin-25+36) with -Xms1g -Xmx1g -XX:+UseG1GC. It measures the average execution time per operation in nanoseconds, running in a shared benchmark state, with 2 forks (each doing 1 warmup fork), warming up for 5 iterations of 1 second each, and then measuring over 10 iterations of 1 second each.
The Two-Pointer Workhorse
public boolean isPalindrome(String text) {
int left = 0, right = text.length() - 1;
while (left < right) {
if (text.charAt(left) != text.charAt(right)) return false;
left++;
right--;
}
return true;
}The classic solution. Tight loop, minimal overhead, excellent branch prediction.
Highlights:
- Short palindromes: 5.9 ns/op
- Non-palindromes: ~4 ns/op
- Long palindromes: slower at 64.8 ns/op
Why it works: The moment characters differ, the loop exits. Non-palindromes benefit the most.
Half-String Comparison: The Minimalist Variant
int half = length / 2;
for (int i = 0; i < half; i++) {
if (text.charAt(i) != text.charAt(length - 1 - i)) return false;
}
Checks only half the string — no pointer tracking overhead.
Highlights:
- Best for medium strings: 9.7 ns/op
- Beats two-pointer for long palindromes too
Small optimization, big payoff for palindrome-heavy workloads.
Recursion: Surprisingly Good
private boolean isPalindromeRecursive(String text, int left, int right) {
if (left >= right) return true;
if (text.charAt(left) != text.charAt(right)) return false;
return isPalindromeRecursive(text, left + 1, right - 1);
}Recursion is rarely considered “modern Java”, but here? It’s fast.
Highlights:
- Short palindromes: 5.3 ns/op (faster than two-pointer!)
- Medium non-palindromes: 3.9 ns/op (fastest!)
Don’t use this on very long strings — stack overflow risk.
Char Arrays: Pay the Copy Once
char[] chars = text.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
if (chars[left] != chars[right]) return false;
left++; right--;
}
Copying the string costs time, but once you have the char[], access is extremely fast.
Highlights:
- Long palindromes: 31.6 ns/op (best in class)
- Short strings: slower due to array creation
StringBuilder Reverse: The “Readable” Option
String reversed = new StringBuilder(text).reverse().toString();
return text.equals(reversed);
The most “Java developer” solution.
Benchmarks:
- Short strings: 27 ns/op
- Long strings: 37–42 ns/op
Not terrible, not great. Predictable overhead, but extremely readable.
Stream API: Convenience vs Cost
return IntStream.range(0, length / 2)
.allMatch(i -> text.charAt(i) == text.charAt(length - 1 - i));
Modern, expressive and slow.
Benchmarks:
- Medium palindromes: 87.7 ns/op
- Long palindromes: 404 ns/op
The Insight: Early Exit Wins
Most algorithms perform extremely well on non-palindromes, because they detect the mismatch at the first or second character.
This yields:
- 4 ns/op for pointer-based methods
- 10× slower for actual palindromes (which require full traversal)
If your workload is mostly non-palindromic strings, almost anything performs fine.
Recommended Algorithms (Based on Input Characteristics)
Use Two-Pointer If…
- Input size varies
- You want the best overall performance
- You expect many non-palindromes
Use Recursion If…
- Inputs are short
- Code clarity matters
- Stack depth is predictable
Use StringBuilder If…
- Readability > raw performance
Final Takeaways
- Even trivial algorithms deserve benchmarking — intuition is rarely enough.
- Early exit has a dominant effect on actual performance.
- There is no single “fastest” implementation, workload drives the optimal choice.
- Pointer-based approaches remain the most reliable performers in Java.
You can find all the code on GitHub.
Originally posted on marconak-matej.medium.com.