1. Overview

In this tutorial, we’re going to learn a handful of ways to replace part of a string with something else in Kotlin.

2. Replacing Single Characters

In order to replace a single character in a string, we can use the replace(oldChar, newChar) extension function:

val txt = "I am a robot"
val replaced = txt.replace('a', 'A')
assertEquals("I Am A robot", replaced)

As shown above, this particular method is pretty straightforward, as it simply replaces all the occurrences of the first argument with the second argument.

Please note that all the variations of the replace extension function in Koltin will return a new string instead of changing it in place. This is because of the fact that strings are immutable in Kotlin.

By default, the replace(oldChar, newChar) method does care about case sensitivity. However, we can disable this setting by passing an optional ignoreCase parameter as the last argument:

val replaced = txt.replace('i', 'i', ignoreCase = true)
assertEquals("i am a robot", replaced)

Here, we’re looking for both lowercase and uppercase ‘i’ characters (as specified by the first and third arguments) and replacing them with the lowercase one (the second argument).

3. Replacing Substrings

In addition to single characters, it’s also possible to replace a substring with something else in the target string:

val txt = "I am a robot"
val replaced = txt.replace("robot", "human")
assertEquals("I am a human", replaced)

As shown above, the whole “robot” string is replaced with “human”. Similarly, we can enable the case-insensitive version by passing a boolean flag as the last argument:

val replaced = txt.replace("i am a", "we are", true)
assertEquals("we are robot", replaced)

4. Replacing with Regex

In addition to a simple sequence of characters, we can replace a pattern using regular expressions:

val txt = "<div>This is a div tag</div>"
val regex = "</?.*?>".toRegex() // matches with every <tag> or </tag>
val replaced = txt.replace(regex, "")
assertEquals("This is a div tag", replaced)

Here, replace(pattern, newString) replaces all the occurrences of HTML tags with an empty string. Basically, we’re stripping all HTML tags away.

Quite interestingly, we can pass a *(*MatchResult) -> CharSequence lambda to determine the replacement string based on the matched part. For instance, here, we’re just making the matched part uppercase:

val replaced = txt.replace(regex) {
    it.value.toUpperCase()
}
assertEquals("<DIV>This is a div tag</DIV>", replaced)

As shown above, we can determine the replacement string dynamically by returning whatever we want in the given lambda.

5. Replacing the First Occurrence

So far, we’ve replaced all the occurrences of a single character, a string, or a pattern, with the replacement strings. In addition to this, it’s also possible to replace just the first occurrence with some specialized, overloaded extension functions.

In order to replace the first occurrence of a single character with another character, we can use the replaceFirst(oldChar, newChar) extension function:

val txt = "I am a robot"
val replaced = txt.replaceFirst('a', 'A')
assertEquals("I Am a robot", replaced)

As shown above, only the first ‘a’ is replaced with its uppercase version. Quite similarly, there’s a variant for replacing the first matched substring with another string:

val txt = "42 42"
val replaced = txt.replaceFirst("42", "The answer is")
assertEquals("The answer is 42", replaced)

Both versions are, by default, case sensitive and also support the capability of turning them off. And finally, there is an overload version for replacing the first occurrence of a regular expression:

val txt = "<div>this is a div</div>"
val replaced = txt.replaceFirst("</?.*?>".toRegex(), "")
assertEquals("this is a div</div>", replaced)

Quite understandably, only the first HTML tags have been stripped here.

6. Range Replacement

It’s also even possible to replace some range of characters in a string with something else in a few different ways. Let’s get familiar with these approaches.

6.1. Numerical Range

In order to replace a range of characters, we can use the replaceRange(startIndex, endIndex, replacement) extension function:

val txt = "42 is the answer"
val replaced = txt.replaceRange(0, 3, "")
assertEquals("is the answer", replaced)

As usual, the startIndex is inclusive, and the endIndex is exclusive. Therefore, we’re basically replacing the first three characters with an empty string in this example.

Since Kotlin has first-class support for ranges, we can use the IntRange syntax using the replaceRange(intRange, replacement) extension function:

assertEquals("is the answer", txt.replaceRange(0..2, ""))
assertEquals("is the answer", txt.replaceRange(0 until 3, ""))

In the first example, the “0..2” creates a closed range (both starting and ending indices are inclusive). Therefore, the first example replaces the first three characters with an empty string. On the other hand, the second example creates an open range, so the ending position is exclusive. Therefore, in order to achieve the same output, we should use the “0 until 3” syntax.

6.2. Before Range

In addition to explicit int ranges, we can replace everything before a character or string with a replacement string:

assertEquals("is the answer", txt.replaceBefore('i', ""))
assertEquals("is the answer", txt.replaceBefore("is", ""))

In the above examples, all the characters before the first occurrence of character ‘i’ and string “is” will be replaced with an empty string.

By default, when there isn’t a match for the given search character or string, replaceBefore() will return the whole string as-is:

assertEquals("42 is the answer", txt.replaceBefore("not a match", ""))

However, it’s also possible to return another string in case of a non-match by passing a third argument:

assertEquals("default", txt.replaceBefore("not a match", "", "default"))

As shown above, since there’s no match for the given string, the function returns the “default” string.

By default, the replaceBefore() function replaces everything before the first occurrence of the given argument. It’s also possible to do the same thing based on the last occurrence of given characters using the replaceBeforeLast() overload version:

assertEquals("swer", txt.replaceBeforeLast('s', ""))

Here, the ‘s’ character occurs twice in the text, and the above function picks the last occurrence as the ending point for replacement.

6.3. After Range

Quite similar to replaceBefore(), there are some extension functions that can work the other way around. That is, it’s possible to replace all characters after the first or last occurrence of a particular character or string:

assertEquals("42 i", txt.replaceAfter('i', ""))
assertEquals("42 is", txt.replaceAfter("is", ""))
assertEquals("42 is the answer", txt.replaceAfter("not a match", ""))
assertEquals("default", txt.replaceAfter("not a match", "", "default"))
assertEquals("42 is the ans", txt.replaceAfterLast('s', ""))

As shown above, these overloaded functions are quite similar to what we saw in the previous section.

7. Replacing Indentations

We can remove the starting indents (sentences starting with one or more spaces) using the replaceIndent() extension function:

assertEquals("starts with indent", "    starts with indent".replaceIndent())

As shown above, the replaceIndent() function removes the few extra spaces at the beginning of the sentence. Quite interestingly, we can even replace these indents with other strings, too:

assertEquals("==> starts with indent", "    starts with indent".replaceIndent("==> "))

Here, we’re replacing the extra spaces at the start with the “==> ” marker.

8. Miscellaneous Functions

There are some functions that don’t have a replace prefix in their names, but some of them are related to replace-functionality. In this section, we’re going to get familiar with a few of them.

8.1. Trimming

The trim-related APIs will help us to remove some sets of characters from the beginning or end of a string. In the previous sections, we saw that we could replace all the space characters with an empty string:

assertEquals("both ends", " both ends ".replace(" ", ""))

This way, we’re basically trimming the extra spaces from both ends of the string. The good news is, there are much easier ways to achieve this in Kotlin, thanks to the flexibility of extension functions.

For instance, the trim() extension function removes the extra spaces from both ends of a string:

assertEquals("both ends", "  both ends ".trim())

This function, by default, looks for space characters, but we can change the trim character:

assertEquals("both ends", "###both ends!!".trim('#', '!'))

In the above example, we’re explicitly telling trim() to look for hashtags and exclamation marks, instead of spaces, as trim characters. It’s even possible to pass a predicate lambda to dynamically determine the trim character:

assertEquals("both ends", "#?!both ends@".trim { !it.isLetter() && it != ' ' })

Here, all characters except for letters and space characters are considered as trim characters.

As opposed to trim(), the trimStart() function removes space characters (by default) only from the beginning of a string:

assertEquals("just the beginning  ", "  just the beginning  ".trimStart())
assertEquals("just the beginning##", "##just the beginning##".trimStart('#'))
assertEquals("just the beginning  ", " #%just the beginning  ".trimStart { !it.isLetter() })

As shown above, even though it removes the space characters from the beginning of strings, the trim characters are configurable. We can do the same thing to the other end of strings using the trimEnd() extension function:

assertEquals("  just the ending", "  just the ending  ".trimEnd())
assertEquals("##just the beginning", "##just the beginning##".trimEnd('#'))
assertEquals(" #%just the beginning", " #%just the beginning  ".trimEnd { !it.isLetter() })

The good news is that the trim-related APIs are quite similar to each other in terms of API signature. So, all of them, by default, will remove the space character. However, the trim character can be changed to a set of static characters or be determined by a lambda function dynamically.

8.2. Removing

So far, we’ve used a few replacement methods to replace some parts of a string with an empty string. Instead of explicitly mentioning the empty string as a replacement, we can simply remove the targeted parts from the string with some special functions.

Similar to trimStart(), there is another extension function called removePrefix(prefix) that can remove a prefix from any string:

assertEquals("single line comment", "//single line comment".removePrefix("//"))

Here, we’re removing the “//” marker from the start of the given string. Similarly, we can remove a specific suffix from a string:

assertEquals("end of multiline comment", "end of multiline comment*/".removeSuffix("*/"))

Moreover, it’s possible to remove from both the start and the end of a string:

assertEquals("some regex", "/some regex/".removeSurrounding("/"))

The above function works if and only if the string starts with and ends with the given argument. Otherwise, it will return the same string untouched:

assertEquals("/sample", "/sample".removeSurrounding("/"))

Since the string doesn’t end with a “/”, removeSurrounding() returns the same string. In order to remove different prefixes and suffixes from both ends of a string, we can use the removeSurrounding(prefix, suffix) extension function:

assertEquals("multiline comment", "/*multiline comment*/".removeSurrounding("/*", "*/"))

Similarly, this function only works if the string starts with the given prefix and ends with the given suffix.

Finally, it’s also possible to remove a range of characters in the given string using the removeRange() extension function:

assertEquals("a robot", "I'm a robot".removeRange(0..3))
assertEquals("a robot", "I'm a robot".removeRange(0 until 4))

Similar to replaceRange(), this function accepts Kotlin’s range syntax. In the above example, the “0..3” syntax creates a closed range, so the character on the 3rd index will be removed, as well. That’s not the case for the second example, as the “0 until 4” creates an open range.

It’s even possible to pass the start and end indices as separate arguments:

assertEquals("a robot", "I'm a robot".removeRange(0, 4))

Like always, the ending index is exclusive, so the character at the 4th index won’t be removed.

8.3. Dropping Some Characters

The drop(n: Int) extension function removes the first n characters from the beginning of a string:

assertEquals("is the answer", txt.drop(3))

Here, we’re removing the first three characters. On the other way around, we can remove the last n characters, as well:

assertEquals("42 is the", txt.dropLast(7))

It’s even possible to remove characters from the beginning of a string while a condition is true:

assertEquals(" is the answer", txt.dropWhile { it != ' ' })

Here, we’re removing all characters until we meet the first space character. Similarly, we can do the same thing from the other side of the string:

assertEquals("42 is the ", txt.dropLastWhile { it != ' ' })

8.4. Taking Some Characters

As opposed to the drop*() set of extension functions, instead of removing characters, we can keep them using the take*() set of extension functions. For instance, to just keep the first n characters from the beginning of a string, we can use take(n: Int):

assertEquals("42", txt.take(2))

Here, we’re keeping the first two characters and throwing the rest of the string away. The remaining take related APIs are quite similar to what we saw for drop related ones:

assertEquals("answer", txt.takeLast(6))
assertEquals("42", txt.takeWhile { it != ' ' })
assertEquals("answer", txt.takeLastWhile { it != ' ' })

9. Conclusion

Thanks to the flexibility of extension functions, Kotlin provides an extremely rich API for string manipulation, including replacing related functionalities. In this article, we saw quite a lot of ways to replace parts of strings with different replacements in a variety of ways, such as matching with regex or ranges.

As usual, all the examples are available over on GitHub.