Back to Blog
Java

Java StringBuilder delete: Remove Characters Efficiently

java stringbuilder delete: Learn how to use StringBuilder.delete() and deleteCharAt() to remove characters efficiently in Java, with index boundary rules and performan...

StringBuilderJava StringString ManipulationdeleteCharAtMutable Strings
Diagram showing character removal from a mutable string buffer in Java.

When you need to remove characters from a string in Java, the immutable String class forces you to create a new object for every change. The StringBuilder class solves that problem by providing a mutable sequence of characters. The delete() method is one of the most direct ways to remove a range of characters from a StringBuilder without allocating a new string. This article explains how java stringbuilder delete works, how to use it correctly, and where it can trip you up.

Understanding StringBuilder.delete() Signature and Behavior

The delete() method is defined in the AbstractStringBuilder class, which StringBuilder extends. Its signature is:

public StringBuilder delete(int start, int end)

It removes the characters in the range [start, end), meaning the character at index start is included, but the character at index end is not. The method returns the same StringBuilder instance, which allows method chaining. The deletion happens in place, shifting any characters after end left by (end - start) positions.

For example:

StringBuilder sb = new StringBuilder("Hello World"); sb.delete(5, 11); System.out.println(sb.toString()); // Output: Hello

Here, the space and "World" are removed, leaving "Hello". The range starts at index 5 (the space) and ends at index 11 (the character after 'd'), so the entire " World" is removed.

Removing a Range of Characters with delete(int start, int end)

The most common use of delete() is to remove a contiguous block of characters. You need to be precise about the start and end indices. The method follows the same convention as String.substring(): the start index is inclusive, the end index is exclusive.

Consider a scenario where you have a string with a fixed prefix that you want to strip:

StringBuilder logLine = new StringBuilder("ERROR: Disk full"); logLine.delete(0, 6); // Remove "ERROR:" System.out.println(logLine.toString()); // Output: " Disk full"

Notice that the space after the colon remains. To remove the prefix including the space, you would adjust the end index to 7. This off-by-one error is a frequent source of bugs, so always double-check your index calculations.

Another practical use is cleaning up delimiters. If you have a comma-separated list and want to remove the last comma, you can use the length of the builder:

StringBuilder csv = new StringBuilder("apple,banana,cherry,"); csv.delete(csv.length() - 1, csv.length()); System.out.println(csv.toString()); // Output: apple,banana,cherry

Here, end is set to csv.length() to include the last character, because end is exclusive.

Removing a Single Character Using deleteCharAt(int index)

When you only need to remove one character, deleteCharAt(int index) is more concise and avoids the overhead of specifying a range. Its signature is:

public StringBuilder deleteCharAt(int index)

It removes the character at the given index and shifts all subsequent characters left by one. The method also returns the StringBuilder for chaining.

A typical use is removing a specific character from a string, such as a quote or a separator:

StringBuilder sb = new StringBuilder("J\"ava"); sb.deleteCharAt(1); System.out.println(sb.toString()); // Output: Java

You can also use it in a loop to remove every occurrence of a character, but be careful: the indices shift after each deletion. If you iterate forward, you will skip characters. The safe approach is to iterate backward:

StringBuilder sb = new StringBuilder("aXbXcX"); for (int i = sb.length() - 1; i >= 0; i--) { if (sb.charAt(i) == 'X') { sb.deleteCharAt(i); } } System.out.println(sb.toString()); // Output: abc

Iterating backward avoids the index-shifting problem because earlier indices remain valid after removing later characters.

Common Mistakes with Index Boundaries

Both delete() and deleteCharAt() throw StringIndexOutOfBoundsException when the provided indices are invalid. The exact rules are:

  • For delete(int start, int end): start must be non-negative, less than the current length, and not greater than end. The end can be equal to the length, but cannot exceed it. If start == end, nothing is removed.
  • For deleteCharAt(int index): index must be between 0 and length() - 1 inclusive.

A frequent mistake is passing a negative index or an index equal to the length. For example:

StringBuilder sb = new StringBuilder("abc"); sb.delete(1, 1); // Legal, but does nothing sb.delete(3, 3); // Legal, but does nothing sb.delete(0, 4); // Throws StringIndexOutOfBoundsException sb.deleteCharAt(3); // Throws StringIndexOutOfBoundsException

Another subtle issue is using delete() with a start that is greater than end. This also throws an exception. Always ensure that start <= end.

When you need to remove the entire content, you might be tempted to call delete(0, length()), but setLength(0) is more efficient because it does not shift characters; it simply resets the internal count. Use delete() for partial removal and setLength(0) for clearing.

Performance and Memory Considerations

The main advantage of StringBuilder.delete() over String concatenation or substring() is that it mutates the existing character array in place. No new String object is created, and the underlying array is reused as long as the capacity is sufficient. This reduces memory allocation and garbage collection pressure, especially in loops.

However, delete() is not free. It calls System.arraycopy() to shift the remaining characters left. The cost is proportional to the number of characters after the deleted range. For example, deleting a character at the beginning of a large StringBuilder is O(n), where n is the length, because all subsequent characters must move. Deleting from the end is O(1).

If you need to delete many characters scattered throughout the string, doing it one by one with deleteCharAt() can become O(n^2) because each deletion shifts the tail. A more efficient strategy is to build a new StringBuilder with only the characters you want to keep, or to use delete() on larger contiguous ranges when possible.

Another performance consideration is capacity. After many deletions, the StringBuilder may have a larger capacity than needed. If memory usage is a concern, you can call trimToSize() to reduce the capacity to the current length, though this forces a new backing array allocation.

When to Prefer StringBuilder over String for Deletion

If you are only removing characters once and the string is short, using String methods like replace() or substring() is perfectly acceptable. The overhead of creating a new String is negligible in such cases. But when you are performing multiple deletions or other modifications in a loop, StringBuilder becomes the better choice.

Consider a scenario where you need to remove all vowels from a string. With String, you would have to create a new string for each vowel removal, leading to many intermediate objects. With StringBuilder, you can mutate the same object:

String input = "Hello World"; StringBuilder sb = new StringBuilder(input); for (int i = sb.length() - 1; i >= 0; i--) { char c = sb.charAt(i); if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { sb.deleteCharAt(i); } } String result = sb.toString();

This approach avoids creating multiple intermediate strings and is more memory-efficient. The rule of thumb is: if you need to modify a string more than once, use StringBuilder.

Handling Edge Cases: Empty String, Out-of-Bounds, and Chained Calls

delete() and deleteCharAt() behave predictably on edge cases, but you need to be aware of them.

If the StringBuilder is empty, any call to deleteCharAt() throws an exception. Calling delete(0, 0) is legal and does nothing, but delete(0, 1) throws because end exceeds the length.

When you chain delete() calls, the return value is the same StringBuilder instance, so you can do:

StringBuilder sb = new StringBuilder("abcdef"); sb.delete(0, 1).delete(1, 2).delete(2, 3); System.out.println(sb.toString()); // Output: def

But be careful: the indices refer to the current state of the builder after each deletion. In the example above, after the first deletion, the builder becomes "bcdef". The second delete(1, 2) removes the character at index 1 (which is 'c'), resulting in "bdef". The third delete(2, 3) removes the character at index 2 (which is 'e'), yielding "bdf". The output is actually "bdf", not "def". Let's correct that:

StringBuilder sb = new StringBuilder("abcdef"); sb.delete(0, 1); // "bcdef" sb.delete(1, 2); // "bdef" sb.delete(2, 3); // "bdf" System.out.println(sb.toString()); // Output: bdf

Chaining works, but it is easy to lose track of the current indices. For clarity, it is often better to perform deletions step by step, or to compute indices based on the original string if you are removing fixed positions.

Another edge case is using delete() with start equal to length(). This is legal if end is also equal to length(), but it does nothing. If start is greater than length(), an exception is thrown.

Finally, remember that StringBuilder is not thread-safe. If multiple threads access the same StringBuilder instance, you must synchronize externally. For concurrent string building, consider using StringBuffer, which has the same delete() methods but with synchronized methods, at the cost of some performance.

Understanding these boundary conditions and the underlying mechanics of delete() will help you avoid subtle bugs and write more efficient string manipulation code in Java.

java stringbuilder delete: Practical Usage and Code Examples | RYUSLOG DEV