chars()
Streams the chars of a String as an IntStream for counting, checking and grouping, and shows when codePoints() is the right call instead.
public IntStream chars()- Added in Java Java 8
Return value
IntStreamAn IntStream of the char values of the String, each widened to an int. Surrogate pairs appear as two separate values.
Errors
Nothing. Calling it on a null reference throws NullPointerException.
Changelog
- PHP Java 9String overrides the CharSequence default with its own implementation for compact strings. The behaviour is unchanged.
chars() returns an IntStream with one element per char. It is declared on CharSequence (Java 8), so it also works on StringBuilder. Each element is an int, not a char: there is no CharStream in the JDK.
Working with ints
Methods such as Character.isDigit, Character.isUpperCase and Character.isWhitespace all have int overloads, so they work directly as filters: s.chars().filter(Character::isDigit).count(). When you need characters back, cast: mapToObj(c -> (char) c) gives a Stream<Character>. Printing an element without the cast prints its number, so A shows as 65.
chars() or codePoints()?
chars() gives UTF-16 code units, so an emoji arrives as two values, each half of a surrogate pair. Counting, filtering or rebuilding text from those halves gives wrong counts and broken characters. codePoints() gives one int per Unicode code point and is the right choice for anything user-facing: counting characters in a display name, validating letters in any script, or building text back with StringBuilder::appendCodePoint. Use chars() when you only care about ASCII, such as checking that a slug, header token or hex string uses an allowed alphabet.
Gotchas
"".chars().allMatch(...)istrue: a check over no elements passes. TestisEmpty()separately when empty input is invalid.- A stream is single use. Call
chars()again for a second pass. - For a single check on a short String, a plain
forloop withcharAtis just as clear and avoids stream set-up; the stream version reads better once you count, group or combine conditions.
Validation built from chars() is a good fit for request input: password policy checks, token formats and identifier rules, where a regex would be harder to read.
Example
When to use it
- Checking a password policy, such as at least one digit and one uppercase letter, at signup.
- Validating that a slug, request id or hex token uses only an allowed set of characters.
- Counting character frequencies for a simple analytics or search feature.
Frequently asked questions
Why does chars() return an IntStream instead of a stream of char?
The JDK has no CharStream, so chars are widened to int. Cast with mapToObj(c -> (char) c) when you need Character objects, or use the int overloads of Character methods directly.
What is the difference between chars() and codePoints()?
chars() returns UTF-16 code units, so an emoji is two values. codePoints() returns one value per Unicode code point, which is what users see as one character in most cases.
More examples
Counting character frequencies
mapToObj with a cast turns the ints back into Characters so they can be map keys. A TreeMap keeps the keys sorted, which makes the output stable.
void main() {
String word = "mississippi";
Map<Character, Long> counts = word.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(c -> c, TreeMap::new, Collectors.counting()));
IO.println(counts);
}
// Output:
// {i=4, m=1, p=2, s=4}Validating a slug's alphabet
allMatch reads like the rule it enforces. The isEmpty check is required because allMatch on an empty stream returns true, which would accept an empty slug.
boolean isSlug(String s) {
return !s.isEmpty() && s.chars().allMatch(c ->
(c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-');
}
void main() {
for (String s : List.of("spring-boot-4", "Spring", "", "caf\u00e9")) {
IO.println("\"" + s + "\" " + isSlug(s));
}
}
// Output:
// "spring-boot-4" true
// "Spring" false
// "" false
// "café" falsechars() versus codePoints()
The party emoji is two chars (d83c and df89) but one code point (1f389). The collect call shows how to turn code points back into a String without splitting a character.
void main() {
String name = "Zo\u00EB \uD83C\uDF89";
IO.println(name.length());
IO.println(name.chars().mapToObj(Integer::toHexString).toList());
IO.println(name.codePoints().mapToObj(Integer::toHexString).toList());
String firstTwo = name.codePoints().limit(2)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
IO.println(firstTwo);
}
// Output:
// 6
// [5a, 6f, eb, 20, d83c, df89]
// [5a, 6f, eb, 20, 1f389]
// Zo
Discussion