Back to Blog
Java

How to Use StringBuilder.reverse() in Java

See how StringBuilder.reverse() works in Java: in-place mutation, Unicode surrogate-pair handling, performance, and common pitfalls.

StringBuilderString reversalJava string handlingUnicodePerformance
Diagram showing a StringBuilder object with characters reversed in place, illustrating the Java StringBuilder reverse method.

Java's StringBuilder.reverse() reverses the character sequence in a mutable StringBuilder in place and returns the same instance, so calls can be chained. It treats a valid UTF-16 surrogate pair as a character for the purpose of reversal, so a string containing an emoji represented by a surrogate pair remains a valid Unicode sequence after reversal.

What StringBuilder.reverse() Actually Does

The reverse() method reverses the order of characters in the StringBuilder. For example, if the current content is "abcde", calling reverse() changes it to "edcba". The method returns the same StringBuilder instance, so you can chain other operations after it:

StringBuilder sb = new StringBuilder("hello"); sb.reverse(); System.out.println(sb.toString()); // "olleh"

The reversal happens in place, meaning the original character array is rearranged. No new StringBuilder object is allocated, and the capacity of the underlying buffer remains unchanged. This is a key difference from approaches that create a new String or StringBuilder for each reversal.

How reverse() Handles Unicode and Surrogate Pairs

Java strings are sequences of UTF-16 code units. Most characters fit in a single char, but supplementary characters (such as emojis) are represented as a pair of char values: a high surrogate followed by a low surrogate. The reverse() method is documented to treat a valid surrogate pair as a single character for the reverse operation, so the high-low order is preserved.

Consider the string "A\uD83D\uDE00B" (which represents A😀B). Reversing it with StringBuilder.reverse() produces B😀A, not an invalid sequence:

String original = "A\uD83D\uDE00B"; // A😀B StringBuilder sb = new StringBuilder(original); sb.reverse(); System.out.println(sb.toString()); // B😀A

The main edge case is malformed UTF-16 data: if the sequence contains an unpaired surrogate, reverse() has no valid pair to protect. Reversing malformed input can leave unpaired surrogates, and in some cases it can even create a valid pair from an unpaired low surrogate followed by an unpaired high surrogate. For well-formed Unicode text, however, reverse() handles supplementary characters correctly.

If you want an explicit code-point-oriented reversal, you can use String.codePoints():

String original = "A\uD83D\uDE00B"; int[] codePoints = original.codePoints().toArray(); StringBuilder reversed = new StringBuilder(); for (int i = codePoints.length - 1; i >= 0; i--) { reversed.appendCodePoint(codePoints[i]); } System.out.println(reversed.toString()); // B😀A

This produces the same result for well-formed input and gives you an explicit code-point view if you need to control how malformed surrogate code points are handled. It is not required for ordinary strings containing emoji.

Performance Considerations of StringBuilder.reverse()

The reverse() method runs in linear time, O(n), where n is the number of char values in the sequence. It swaps from the ends toward the middle, and it does not allocate a new buffer for the reversed sequence. If a surrogate is present, the implementation may perform an additional scan to restore valid surrogate pairs; that scan is also linear and does not allocate additional memory for the sequence.

This makes reverse() more memory-efficient than approaches that create a new String or StringBuilder for each reversal. For example, the common idiom new StringBuilder(str).reverse().toString() creates a new StringBuilder and a new String, but it is still often used because it is concise and the overhead is acceptable for small strings.

If you are reversing very large strings or performing many reversals in a loop, using reverse() on an existing StringBuilder avoids repeated allocations. However, if you only need the reversed result once, the convenience of the one-liner is usually fine.

Common Mistakes When Using reverse()

One frequent mistake is assuming that reverse() returns a new StringBuilder and that the original remains unchanged. Since it mutates the instance, you must be careful if you need to preserve the original order. For example:

StringBuilder sb = new StringBuilder("abc"); StringBuilder reversed = sb.reverse(); // sb is also reversed System.out.println(sb); // "cba" System.out.println(reversed); // "cba"

If you need to keep the original, create a copy before calling reverse():

StringBuilder original = new StringBuilder("abc"); StringBuilder reversed = new StringBuilder(original).reverse();

Another mistake is using StringBuffer instead of StringBuilder. StringBuffer is thread-safe but has synchronized methods, which add overhead. In single-threaded code, StringBuilder is preferred. The reverse() method exists on both classes, but the behavior is identical; the performance difference is due to synchronization.

Alternatives to StringBuilder.reverse()

For simple cases, new StringBuilder(str).reverse().toString() is a concise way to reverse a String. It handles valid surrogate pairs correctly, so it works for strings containing emoji. If you need to treat each Unicode code point explicitly or work with malformed surrogate data, the code-point-based approach is more explicit. A stream-based reduce over chars() is another possibility, but it is not code-point aware and creates many intermediate strings; the StringBuilder approach is almost always better for performance and readability.

If you need to reverse only a portion of the sequence, extract that substring, reverse it separately, and replace the original portion using replace():

StringBuilder sb = new StringBuilder("abcdef"); int start = 1; int end = 5; // exclusive String reversedPart = new StringBuilder(sb.substring(start, end)).reverse().toString(); sb.replace(start, end, reversedPart); System.out.println(sb); // "aedcbf"

Note that subSequence() returns a CharSequence, so it is not useful for reversing a range in place; the substring-and-replace approach above is clearer.

Thread Safety and StringBuffer vs StringBuilder

StringBuilder is not thread-safe. If multiple threads access the same StringBuilder instance concurrently, and at least one thread calls reverse() or any other mutating method, the results are unsafe. For concurrent use, you should either synchronize externally or use StringBuffer, which synchronizes its methods.

StringBuffer.reverse() behaves the same as StringBuilder.reverse() with respect to surrogate pairs, but with the overhead of synchronized methods. In single-threaded code, prefer StringBuilder to avoid unnecessary synchronization. In multi-threaded scenarios, consider whether you can avoid sharing a mutable sequence altogether; often it is better to use immutable String values.

When to Use reverse() vs Manual Reversal

For most practical purposes, StringBuilder.reverse() is the simplest and most efficient way to reverse a string in Java. A manual loop that swaps char values is similar to what reverse() does internally, but using the built-in method handles valid surrogate pairs and reduces the chance of off-by-one errors.

Manual reversal may be necessary if you need to reverse only certain characters or apply custom rules, such as ignoring punctuation or reversing word order. In those cases, you would not use reverse() directly but would build a custom algorithm. However, for a plain character reversal, reverse() is the right tool.

One edge case to keep in mind is that reverse() works on the entire sequence. If you need to reverse the order of words in a sentence, you would split the string into words, reverse the list, and join them back. That is a different operation and should not be confused with character reversal.

Compatibility and Version Considerations

The reverse() method has been part of StringBuilder since Java 5, when StringBuilder was introduced as a non-synchronized alternative to StringBuffer. The behavior is stable across later Java versions, including surrogate-pair handling. No imports are needed because StringBuilder is in java.lang.

When targeting older Java versions, note that StringBuilder does not exist in Java 1.4 and earlier; you would use StringBuffer instead. For modern Java (8 and later), StringBuilder is the standard choice.

If you are working with a large text buffer and need to reverse it frequently, consider the memory and CPU tradeoffs. The in-place nature of reverse() is efficient, but if the buffer is shared across threads, the lack of thread safety can cause corruption. Always document the concurrency assumptions of your code.

StringBuilder.reverse() in Java: Examples and Pitfalls | RYUSLOG DEV