How to Use Guava's Joiner and Splitter for String Manipulation
Guava's Joiner and Splitter utilities provide immutable, thread-safe mechanisms for concatenating collections into delimited strings and parsing delimited text back into lists or maps.
Google Guava's com.google.common.base package contains production-ready utilities for handling delimited text without manual string concatenation or parsing loops. The Joiner class efficiently combines elements into a single string, while Splitter provides lazy, iterator-based parsing of delimited sequences. This guide demonstrates how to use Guava's Joiner and Splitter based on the implementation in the google/guava repository.
Creating and Configuring Joiner Instances
The Joiner class uses a static factory pattern to create configured instances. Call Joiner.on(String separator) to specify the delimiter that will appear between elements.
Configuration methods return new immutable instances rather than modifying the original. This design allows safe storage of Joiner instances as static final constants.
// Basic joining with semicolon separator
Joiner joiner = Joiner.on("; ");
String result = joiner.join("Harry", "Ron", "Hermione");
// Result: "Harry; Ron; Hermione"
Handling Null Values
By default, Joiner throws a NullPointerException if any element is null. Control this behavior using two configuration methods defined in guava/src/com/google/common/base/Joiner.java:
skipNulls() returns a new Joiner that silently ignores null elements during iteration.
useForNull(String nullText) returns a new Joiner that substitutes the provided string for any null value.
List<String> names = List.of("Harry", null, "Ron");
// Skip nulls entirely
String skipped = Joiner.on("; ").skipNulls().join(names);
// → "Harry; Ron"
// Replace nulls with placeholder
String replaced = Joiner.on(",").useForNull("<null>").join(names);
// → "Harry,<null>,Ron"
Appending to Existing Buffers
For high-performance scenarios, use appendTo(Appendable, Iterator) instead of join() to write directly to a StringBuilder, Writer, or other Appendable without creating intermediate strings. The core implementation in Joiner.java (lines 99-112) iterates through the supplied iterator, converting each element via toString(Object) and inserting the separator between items.
StringBuilder sb = new StringBuilder("Names: ");
Joiner.on(", ").appendTo(sb, "Alice", "Bob", "Charlie");
// sb contains: "Names: Alice, Bob, Charlie"
Joining Maps with MapJoiner
Convert a Joiner into a MapJoiner by calling withKeyValueSeparator(String kvSeparator). This inner class reuses the parent Joiner's configuration for handling nulls while appending the key-value separator between each map entry. The concatenation logic resides in MapJoiner.appendTo(Appendable, Iterator) (lines 124-138) within Joiner.java.
Map<String, String> params = Map.of("q", "guava", "page", "1");
String query = Joiner.on("&")
.withKeyValueSeparator("=")
.join(params);
// → "q=guava&page=1"
Creating and Using Splitter
The Splitter class provides the inverse operation, parsing delimited text into an Iterable<String>. Like Joiner, it uses a static factory method on(String) or on(CharMatcher) to create immutable instances.
The splitter holds a Strategy interface implementation that creates a SplittingIterator. This iterator lazily extracts substrings by locating separator positions, avoiding unnecessary memory allocation until the iterator is advanced.
String csv = "foo,bar,baz";
Iterable<String> parts = Splitter.on(',').split(csv);
// Iterates over: ["foo", "bar", "baz"]
Configuring Split Behavior
Chain configuration methods to modify splitting logic. Each method returns a new Splitter instance preserving immutability:
trimResults()removes leading and trailing whitespace from each substringomitEmptyStrings()excludes empty strings from the resultlimit(int maxItems)stops splitting after the specified number of items
The lazy evaluation occurs in SplittingIterator.computeNext() (lines 555-587) in guava/src/com/google/common/base/Splitter.java, which calculates separatorStart and separatorEnd positions on demand.
String messy = " foo, , bar,,baz ";
List<String> clean = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.splitToList(messy);
// → ["foo", "bar", "baz"]
Fixed-Length and Pattern-Based Splitting
For specialized parsing, use alternative factory methods:
Splitter.fixedLength(int length) splits the input into segments of the specified character length.
Splitter.on(Pattern pattern) or onPattern(String regex) uses regular expressions to identify separators.
// Split hex string into byte pairs
String hex = "A1B2C3D4E5";
List<String> bytes = Splitter.fixedLength(2).splitToList(hex);
// → ["A1", "B2", "C3", "D4", "E5"]
// Split on whitespace using regex
Pattern whitespace = Pattern.compile("\\s+");
Iterable<String> words = Splitter.on(whitespace)
.omitEmptyStrings()
.split("a b\tc\nd");
// → ["a", "b", "c", "d"]
Splitting into Maps
Create a MapSplitter by calling withKeyValueSeparator(String) on a Splitter instance. This splits the input into entries, then divides each entry into key-value pairs using the specified separator.
The MapSplitter.split(CharSequence) method (lines 506-524) in guava/src/com/google/common/base/Splitter.java performs validation to detect duplicate keys or malformed entries (missing key-value separators).
String kv = "x=1; y=2; z=3";
Map<String, String> map = Splitter.on(';')
.trimResults()
.withKeyValueSeparator("=")
.split(kv);
// → {x=1, y=2, z=3}
Design Principles and Performance
Both utilities follow consistent architectural patterns that make them suitable for production use:
Immutability – Configuration methods never mutate the original instance. This thread-safety allows storing configured instances as constants and sharing them across threads without synchronization.
Lazy Evaluation – Splitter.split() returns an Iterable whose SplittingIterator produces substrings on demand. This minimizes memory allocation when processing large inputs or when only partial results are needed.
Appendable Integration – Joiner.appendTo() enables zero-copy writing to output buffers, eliminating temporary string creation when building large text outputs.
Summary
- Use
Joiner.on(separator)to create immutable concatenation utilities that handle nulls viaskipNulls()oruseForNull(). - Write directly to buffers using
Joiner.appendTo(Appendable, Iterator)to avoid intermediate string allocation. - Parse delimited text lazily with
Splitter.on(separator), which returns an iterable usingSplittingIteratorfor on-demand processing. - Chain
trimResults()andomitEmptyStrings()to clean parsed data without manual post-processing. - Convert between formats using
MapJoiner(viawithKeyValueSeparator) for query strings andMapSplitterfor configuration parsing. - Both classes reside in
guava/src/com/google/common/base/and store configuration in immutable instances safe for static reuse.
Frequently Asked Questions
How do I handle null values when joining strings in Guava?
By default, Joiner throws a NullPointerException if it encounters a null element. Call skipNulls() to ignore null values entirely, or use useForNull(String replacement) to substitute a specific string for nulls. Both methods return a new Joiner instance, leaving the original configuration unchanged.
What is the difference between split() and splitToList() in Guava's Splitter?
The split() method returns an Iterable<String> that lazily evaluates the input as you iterate, making it memory-efficient for large datasets. The splitToList() method immediately consumes the entire input and returns an immutable List<String>, which is convenient when you need random access or guaranteed complete parsing.
Can I use regular expressions with Guava Splitter?
Yes. Use Splitter.on(Pattern pattern) to pass a compiled java.util.regex.Pattern, or use Splitter.onPattern(String regex) to provide the regular expression as a string. This allows splitting on complex separators like whitespace (\\s+) or multiple delimiter characters.
Is Guava's Joiner thread-safe?
Yes. Joiner instances are immutable and thread-safe. Configuration methods such as skipNulls() or useForNull() return new instances rather than modifying the existing object, allowing you to store Joiner configurations as static final constants and share them safely across multiple threads.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →