Over a million developers have joined DZone.

Java Implementation of String#next() Successor

DZone's Guide to

Java Implementation of String#next() Successor

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

I've found the Ruby's String#next() or #succ very useful and productive, specially when generating data for testing. Here is what the Ruby doc says:

succ -> new_str

next -> new_str

Returns the successor to str. The successor is calculated by incrementing characters starting from the rightmost alphanumeric (or the rightmost character > if there are no alphanumerics) in the string. Incrementing a digit always results in another digit, and incrementing a letter results in another letter > of the same case. Incrementing nonalphanumerics uses the underlying character set’s collating sequence.

If the increment generates a “carry,” the character to the left of it is incremented. This process repeats until there is no carry, adding an additional > character if necessary.

"abcd".succ        #=> "abce"
"THX1138".succ     #=> "THX1139"
"<<koala>>".succ   #=> "<<koalb>>"
"1999zzz".succ     #=> "2000aaa"
"ZZZ9999".succ     #=> "AAAA0000"
"***".succ         #=> "**+"

So when I saw Groovy actually has provided a String extension #next() method, I was happy to try it out. But then I was quickly disappointed when the behavior is very different. The Groovy version is very simple and actually not very productive since it simply loop through Character set range in incrementally (including non-printable characters blindly!). The Ruby's version, however, is much more productive since it produce visible characters. For examples:

bash> ruby -e 'puts "Z".next()'
bash> groovy -e 'println("Z".next())'

I wish Groovy version would improve in future as it's not very useful at the moment. Just for fun, I wrote a Java implementation version that mimics the Ruby's behavior:

package deng.jdk;

 * Utilities method for manipulating String.
 * @author zemian 1/1/13
public class StringUtils {

    /** Calculate string successor value. Similar to Ruby's String#next() method. */
    public static String next(String text) {
        // We will not process empty string
        int len = text.length();
        if (len == 0)
            return text;

        // Determine where does the first alpha-numeric starts.
        boolean alphaNum = false;
        int alphaNumPos = -1;
        for (char c : text.toCharArray()) {
            if (Character.isDigit(c) || Character.isLetter(c)) {
                alphaNum = true;

        // Now we go calculate the next successor char of the given text.
        StringBuilder buf = new StringBuilder(text);
        if (!alphaNum || alphaNumPos == 0 || alphaNumPos == len) {
            // do the entire input text
            next(buf, buf.length() - 1, alphaNum);
        } else {
            // Strip the input text for non alpha numeric prefix. We do not need to process these prefix but to save and
            // re-attach it later after the result.
            String prefix = text.substring(0, alphaNumPos);
            buf = new StringBuilder(text.substring(alphaNumPos));
            next(buf, buf.length() - 1, alphaNum);
            buf.insert(0, prefix);

        // We are done.
        return buf.toString();

    /** Internal method to calculate string successor value on alpha numeric chars only. */
    private static void next(StringBuilder buf, int pos, boolean alphaNum) {
        // We are asked to carry over next value for the left most char
        if (pos == -1) {
            char c = buf.charAt(0);
            String rep = null;
            if (Character.isDigit(c))
                rep = "1";
            else if (Character.isLowerCase(c))
                rep = "a";
            else if (Character.isUpperCase(c))
                rep = "A";
                rep = Character.toString((char) (c + 1));
            buf.insert(0, rep);

        // We are asked to calculate next successor char for index of pos.
        char c = buf.charAt(pos);
        if (Character.isDigit(c)) {
            if (c == '9') {
                buf.replace(pos, pos + 1, "0");
                next(buf, pos - 1, alphaNum);
            } else {
                buf.replace(pos, pos + 1, Character.toString((char)(c + 1)));
        } else if (Character.isLowerCase(c)) {
            if (c == 'z') {
                buf.replace(pos, pos + 1, "a");
                next(buf, pos - 1, alphaNum);
            } else {
                buf.replace(pos, pos + 1, Character.toString((char)(c + 1)));
        } else if (Character.isUpperCase(c)) {
            if (c == 'Z') {
                buf.replace(pos, pos + 1, "A");
                next(buf, pos - 1, alphaNum);
            } else {
                buf.replace(pos, pos + 1, Character.toString((char)(c + 1)));
        } else {
            // If input text has any alpha num at all then we are to calc next these characters only and ignore the
            // we will do this by recursively call into next char in buf.
            if (alphaNum) {
                next(buf, pos - 1, alphaNum);
            } else {
                // However if the entire input text is non alpha numeric, then we will calc successor by simply
                // increment to the next char in range (including non-printable char!)
                if (c == Character.MAX_VALUE) {
                    buf.replace(pos, pos + 1, Character.toString(Character.MIN_VALUE));
                    next(buf, pos - 1, alphaNum);
                } else {
                    buf.replace(pos, pos + 1, Character.toString((char)(c + 1)));

And here is my unit test for sanity check:

package deng.jdk;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;

import java.lang.Exception;

public class StringUtilsTest {

    public void testNext() throws Exception {
        Assert.assertThat(StringUtils.next("abcd"), Matchers.is("abce"));
        Assert.assertThat(StringUtils.next("THX1138"), Matchers.is("THX1139"));
        Assert.assertThat(StringUtils.next("<<koala>>"), Matchers.is("<<koalb>>"));
        Assert.assertThat(StringUtils.next("1999zzz"), Matchers.is("2000aaa"));
        Assert.assertThat(StringUtils.next("ZZZ9999"), Matchers.is("AAAA0000"));
        Assert.assertThat(StringUtils.next("***"), Matchers.is("**+"));

        // Test next continually
        String s = "00";
        for (int i = 0; i < 10 * 10; i++)
            s = StringUtils.next(s);
        Assert.assertThat(s, Matchers.is("100"));
        s = "AA";
        for (int i = 0; i < 26 * 26; i++)
            s = StringUtils.next(s);
        Assert.assertThat(s, Matchers.is("AAA"));
        s = "AA00";
        for (int i = 0; i < 26 * 26 * 10 * 10; i++)
            s = StringUtils.next(s);
        Assert.assertThat(s, Matchers.is("AAA00"));

        // Test some corner cases
        Assert.assertThat(StringUtils.next(""), Matchers.is(""));
        Assert.assertThat(StringUtils.next(" "), Matchers.is("!"));
        Assert.assertThat(StringUtils.next("#"), Matchers.is("$"));
        Assert.assertThat(StringUtils.next("Z"), Matchers.is("AA"));
        Assert.assertThat(StringUtils.next("#Z"), Matchers.is("#AA"));
        Assert.assertThat(StringUtils.next("#Z#"), Matchers.is("#AA#"));
        Assert.assertThat(StringUtils.next("999"), Matchers.is("1000"));


Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat


Published at DZone with permission of Zemian Deng, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.


Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}