Java 11 added five small String methods. None of them is clever, and together they
delete a surprising amount of the utility code every project used to carry — the null-and-whitespace
check, the manual line splitter, the loop that repeats a character.
isBlank()
class Demo {
void run() {
System.out.println("".isEmpty()); // true
System.out.println(" ".isEmpty()); // false — three characters is not empty
System.out.println(" ".isBlank()); // true — but it is blank
System.out.println("\t\n ".isBlank()); // true — tabs and newlines count
System.out.println("a".isBlank()); // false
}
}
isBlank() is what people usually mean when they reach for isEmpty(). A
form field containing three spaces is empty as far as your user is concerned, and
isEmpty() disagrees.
The everyday check, still needing its own null guard:
class Demo {
boolean hasContent(String value) {
return value != null && !value.isBlank();
}
}
strip() and friends
class Demo {
void run() {
String padded = " hello ";
System.out.println("[" + padded.strip() + "]"); // [hello]
System.out.println("[" + padded.stripLeading() + "]"); // [hello ]
System.out.println("[" + padded.stripTrailing() + "]"); // [ hello]
}
}
These replace trim(), and the difference is not cosmetic. trim() was
written in 1995 and removes any character with a code below U+0020 — a definition that predates
Unicode having opinions about whitespace. It does not remove a non-breaking space, an em space, or an
ideographic space, all of which arrive routinely from copy-paste and from Word documents:
class Demo {
void run() {
String nbsp = " hello "; // non-breaking spaces
System.out.println("[" + nbsp.trim() + "]"); // [ hello ] — unchanged
System.out.println("[" + nbsp.strip() + "]"); // [hello]
}
}
strip() uses Character.isWhitespace, which knows about all of it.
Use strip() in new code and treat trim() as legacy. The one
exception is if you specifically need the old behaviour for compatibility with something that already
depends on it.
lines()
class Demo {
void run() {
String text = "first\nsecond\r\nthird\n";
text.lines().forEach(System.out::println); // first, second, third
long count = text.lines().count();
System.out.println(count); // 3 — the trailing newline is not a line
List<String> nonEmpty = text.lines()
.map(String::strip)
.filter(s -> !s.isBlank())
.toList();
System.out.println(nonEmpty.size()); // 3
}
}
It returns a Stream<String>, not an array, so filtering and mapping follow
naturally. It also handles all three line-ending conventions — \n, \r\n and
a bare \r — which is the part that makes split("\n") a bug on Windows
input.
The comparison is worth seeing directly:
class Demo {
void run() {
String text = "a\r\nb\n";
System.out.println(text.split("\n").length); // 2, but "a" still has a trailing \r
System.out.println(text.lines().count()); // 2, clean
}
}
repeat(int)
class Demo {
void run() {
System.out.println("-".repeat(30)); // a separator line
System.out.println("ab".repeat(3)); // ababab
System.out.println("x".repeat(0)); // "" — legal, empty
// Indenting, padding, simple tables
System.out.println(" ".repeat(4) + "indented");
}
}
A negative count throws IllegalArgumentException; zero is fine and gives an empty
string. Before this existed, everyone had a StringUtils.repeat or a loop with a
StringBuilder.
Null safety, which these do not give you
Worth being explicit about, because the names suggest otherwise: none of these methods is null-safe. Calling any of them on a null reference throws, exactly as before:
class Demo {
void run() {
String value = null;
// value.isBlank(); // NullPointerException
System.out.println(Objects.requireNonNullElse(value, "").isBlank()); // true
System.out.println(Optional.ofNullable(value)
.map(String::strip)
.filter(s -> !s.isBlank())
.orElse("(none)")); // (none)
}
}
Java 11 did add Predicate.not, which pairs with these nicely and removes an awkward
lambda:
class Demo {
void run() {
List<String> raw = List.of("Ana", " ", "Bo", "");
// Before: a lambda, because there is no isNotBlank
System.out.println(raw.stream().filter(s -> !s.isBlank()).toList());
// After
System.out.println(raw.stream().filter(Predicate.not(String::isBlank)).toList());
// [Ana, Bo]
}
}
Where they came from, and why it took so long
It is reasonable to ask why a language in its eleventh major version was still adding
repeat. The answer is instructive about how Java evolves.
String is one of the most widely used classes in existence, and every method added to
it is permanent — it can never be removed, and it constrains every future implementation. So the bar
for adding one is high, and the argument is not "this is useful" but "the absence of this is causing
identical code to be written in every project."
By 2018 that was demonstrably true. Apache Commons had StringUtils.isBlank,
repeat and stripStart; Guava had its own; and most codebases without either
had hand-rolled versions with subtly different edge-case behaviour. Pulling the five most-duplicated
helpers into the JDK removed a dependency from a lot of projects that were carrying Commons Lang for
little else.
The practical consequence for you: if you are working in a codebase that still imports
StringUtils for these, the imports can usually go. Check the null behaviour first,
though — StringUtils.isBlank(null) returns true, while
null.isBlank() throws, and that difference has caused real bugs during exactly this
cleanup.
All five, and what they replace
| Method | Returns | Replaces |
|---|---|---|
isBlank() | boolean | trim().isEmpty() |
strip() | String | trim(), correctly |
stripLeading() / stripTrailing() | String | a regex, or a manual index scan |
lines() | Stream<String> | split("\\r?\\n") |
repeat(n) | String | a StringBuilder loop |
All of them are on the String fundamentals post's list of things
worth memorising, and all of them return a new string — String is still immutable, so
an unassigned value.strip(); does nothing at all.
Next
The Java 11 file and collection methods is next — another handful of small additions that each remove a helper class.