Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll see how to wrap a sentence automatically after a given number of characters. Hence, our program will return a transformed String with new line breaks.

2. General Algorithm

Let’s consider the following sentence: Baeldung is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.

We want to insert line returns every n characters maximum, n representing the number of characters. Let’s see the code to do this:

String wrapStringCharacterWise(String input, int n) {      
    StringBuilder stringBuilder = new StringBuilder(input);
    int index = 0;
    while(stringBuilder.length() > index + n) {
        index = stringBuilder.lastIndexOf(" ", index + n);    
        stringBuilder.replace(index, index + 1, "\n");
        index++; 
    }
    return stringBuilder.toString();
}

Let’s take n=20 and understand our example code:

  • we start with finding the latest whitespace before 20 characters: in this case, between the words a and popular
  • then we replace this whitespace with a line return
  • and we start again from the beginning of the next word, popular in our example

We stop the algorithm when the remaining sentence has less than 20 characters. We naturally implement this algorithm via a for loop. Besides, we used a StringBuilder internally for convenience, and parameterized our inputs:

We can write a unit test to confirm that our method returns the expected result for our example:

@Test
void givenStringWithMoreThanNCharacters_whenWrapStringCharacterWise_thenCorrectlyWrapped() {
    String input = "Baeldung is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
    assertEquals("Baeldung is a\npopular website that\nprovides in-depth\ntutorials and\narticles on various\nprogramming and\nsoftware development\ntopics, primarily\nfocused on Java and\nrelated\ntechnologies.", wrapper.wrapStringCharacterWise(input, 20));
}

3. Edge Cases

For now, we’ve written a very naive code. In a real-life use case, we might need to take into account some edge cases. Within this article, we’ll address two of them.

3.1. Words Longer Than the Character Limit

First, what if a word is too large and is impossible to wrap? For simplicity, let’s throw an IllegalArgumentException in this case. At every iteration of our loop, we need to check that there’s indeed a whitespace before the given length:

String wrapStringCharacterWise(String input, int n) {      
    StringBuilder stringBuilder = new StringBuilder(input);
    int index = 0;
    while(stringBuilder.length() > index + n) {
        index = stringBuilder.lastIndexOf(" ", index + n);
        if (index == -1) {
            throw new IllegalArgumentException("impossible to slice " + stringBuilder.substring(0, n));
        }       
        stringBuilder.replace(index, index + 1, "\n");
        index++; 
    }
    return stringBuilder.toString();
}

This time again, we can write a simple JUnit test for validation:

@Test
void givenStringWithATooLongWord_whenWrapStringCharacterWise_thenThrows() {
    String input = "The word straightforward has more than 10 characters";
    assertThrows(IllegalArgumentException.class, () -> wrapper.wrapStringCharacterWise(input, 10));
}

3.2. Original Input With Line Returns

Another edge case is when the input String already has line return characters inside. For the moment, if we add a line return after the word Baeldung in our sentence, it will be wrapped identically. However, it sounds more intuitive to start wrapping after the existing line returns.

For this reason, we’ll search for the last line return at every iteration of our algorithm; if it exists, we move the cursor and skip the wrapping part:

String wrapStringCharacterWise(String input, int n) {      
    StringBuilder stringBuilder = new StringBuilder(input);
    int index = 0;
    while(stringBuilder.length() > index + n) {
        int lastLineReturn = stringBuilder.lastIndexOf("\n", index + n);
        if (lastLineReturn > index) {
            index = lastLineReturn;
        } else {
            index = stringBuilder.lastIndexOf(" ", index + n);
            if (index == -1) {
                throw new IllegalArgumentException("impossible to slice " + stringBuilder.substring(0, n));
            }       
            stringBuilder.replace(index, index + 1, "\n");
            index++;
        }    
    }
    return stringBuilder.toString();
}

Again, we can test our code on our example:

@Test
void givenStringWithLineReturns_whenWrapStringCharacterWise_thenWrappedAccordingly() {
    String input = "Baeldung\nis a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
    assertEquals("Baeldung\nis a popular\nwebsite that\nprovides in-depth\ntutorials and\narticles on various\nprogramming and\nsoftware development\ntopics, primarily\nfocused on Java and\nrelated\ntechnologies.", wrapper.wrapStringCharacterWise(input, 20));
}

4. Apache WordUtils wrap() Method

We can use Apache WordUtils wrap() method to implement the required behavior. First, let’s add the latest Apache commons-text dependency:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.10.0</version>
</dependency>

The main difference with our code is that wrap() uses the platform-independent System‘s line separator by default:

@Test
void givenStringWithMoreThanNCharacters_whenWrap_thenCorrectlyWrapped() {
    String input = "Baeldung is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
    assertEquals("Baeldung is a" + System.lineSeparator() + "popular website that" + System.lineSeparator() + "provides in-depth" + System.lineSeparator() + "tutorials and" + System.lineSeparator() + "articles on various" + System.lineSeparator() + "programming and" + System.lineSeparator() + "software development" + System.lineSeparator() + "topics, primarily" + System.lineSeparator() + "focused on Java and" + System.lineSeparator() + "related" + System.lineSeparator() + "technologies.", WordUtils.wrap(input, 20));
}

By default, wrap() accepts long words but doesn’t wrap them:

@Test
void givenStringWithATooLongWord_whenWrap_thenLongWordIsNotWrapped() {
    String input = "The word straightforward has more than 10 characters";
    assertEquals("The word" + System.lineSeparator() + "straightforward" + System.lineSeparator() + "has more" + System.lineSeparator() + "than 10" + System.lineSeparator() + "characters", WordUtils.wrap(input, 10));
}

Last but not least, our other edge case is ignored by this library:

@Test
void givenStringWithLineReturns_whenWrap_thenWrappedLikeThereWasNone() {
    String input = "Baeldung" + System.lineSeparator() + "is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
    assertEquals("Baeldung" + System.lineSeparator() + "is a" + System.lineSeparator() + "popular website that" + System.lineSeparator() + "provides in-depth" + System.lineSeparator() + "tutorials and" + System.lineSeparator() + "articles on various" + System.lineSeparator() + "programming and" + System.lineSeparator() + "software development" + System.lineSeparator() + "topics, primarily" + System.lineSeparator() + "focused on Java and" + System.lineSeparator() + "related" + System.lineSeparator() + "technologies.", WordUtils.wrap(input, 20));
}

To conclude, we can have a look at the overloaded signature of the method:

static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn)

We notice the additional parameters:

  • newLineStr: to use a different character for new line insertion
  • wrapLongWords: a boolean to decide whether to wrap long words or not
  • wrapOn: any regular expression can be used instead of whitespaces

5. Conclusion

In this article, we saw an algorithm to wrap a String after a given number of characters. We implemented it and added the support for a couple of edge cases.

Lastly, we realized that Apache WordUtils’ wrap() method is highly configurable and should suffice in most cases. However, if we can’t use the external dependency or need specific behaviours, we can use our own implementation.

As always, the code is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.