[MM’s] Tiny Bench Notes — Fast or Pretty? The Java Palindrome Benchmarks

7 Different String-Reversal Algorithms Through JMH to Find the Winner

Fast or Pretty? The Java Palindrome Benchmarks

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.