The Apache Commons Collections package contains types that extend and augment the Java Collections Framework
—
Apache Commons Collections provides comprehensive support for functional-style programming through predicates, transformers, closures, and factories. These functional interfaces enable powerful collection processing and manipulation patterns.
Predicates test objects and return boolean results, used for filtering and validation.
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.PredicateUtils;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
import java.util.Arrays;
import java.util.Collection;
// Create custom predicate
Predicate<Integer> evenPredicate = n -> n % 2 == 0;
Predicate<String> longStringPredicate = s -> s.length() > 5;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<String> words = Arrays.asList("cat", "elephant", "dog", "butterfly", "ant", "hippopotamus");
// Filter collections using predicates
Collection<Integer> evenNumbers = CollectionUtils.select(numbers, evenPredicate);
// Result: [2, 4, 6, 8, 10]
Collection<String> longWords = CollectionUtils.select(words, longStringPredicate);
// Result: ["elephant", "butterfly", "hippopotamus"]
// Test single objects
boolean isEven = evenPredicate.evaluate(4); // true
boolean isLong = longStringPredicate.evaluate("cat"); // falseTransformers convert objects from one type or state to another.
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.TransformerUtils;
import java.util.Collection;
// Simple transformations
Transformer<String, String> upperCase = String::toUpperCase;
Transformer<String, Integer> stringLength = String::length;
Transformer<Integer, String> numberToString = Object::toString;
List<String> words = Arrays.asList("hello", "world", "java", "collections");
// Transform collections
Collection<String> upperCaseWords = CollectionUtils.collect(words, upperCase);
// Result: ["HELLO", "WORLD", "JAVA", "COLLECTIONS"]
Collection<Integer> wordLengths = CollectionUtils.collect(words, stringLength);
// Result: [5, 5, 4, 11]
// Transform single objects
String upper = upperCase.transform("hello"); // "HELLO"
Integer length = stringLength.transform("test"); // 4Closures perform actions on objects without returning values (side effects).
import org.apache.commons.collections4.Closure;
import org.apache.commons.collections4.ClosureUtils;
import org.apache.commons.collections4.IterableUtils;
// Create closures for side effects
Closure<String> printClosure = System.out::println;
Closure<Integer> squarePrintClosure = n -> System.out.println(n + "² = " + (n * n));
List<String> messages = Arrays.asList("Hello", "World", "Java");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Apply closure to each element
IterableUtils.forEach(messages, printClosure);
// Output: Hello, World, Java (each on new line)
IterableUtils.forEach(numbers, squarePrintClosure);
// Output: 1² = 1, 2² = 4, 3² = 9, 4² = 16, 5² = 25
// Execute closure on single object
printClosure.execute("Single message");Factories create new objects on demand.
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.FactoryUtils;
import org.apache.commons.collections4.map.LazyMap;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.time.LocalDateTime;
// Simple factories
Factory<String> constantFactory = () -> "default";
Factory<List<String>> listFactory = ArrayList::new;
Factory<LocalDateTime> timestampFactory = LocalDateTime::now;
// Use factory to create objects
String defaultValue = constantFactory.create(); // "default"
List<String> newList = listFactory.create(); // new ArrayList<>()
LocalDateTime now = timestampFactory.create(); // current timestamp
// Lazy map with factory
Map<String, List<String>> lazyMap = LazyMap.lazyMap(new HashMap<>(), listFactory);
// Lists are created on-demand
lazyMap.get("key1").add("value1"); // Creates new ArrayList automatically
lazyMap.get("key2").add("value2"); // Creates another new ArrayListimport org.apache.commons.collections4.PredicateUtils;
// Common predicates
Predicate<Object> nullPred = PredicateUtils.nullPredicate();
Predicate<Object> notNullPred = PredicateUtils.notNullPredicate();
Predicate<Object> truePred = PredicateUtils.truePredicate();
Predicate<Object> falsePred = PredicateUtils.falsePredicate();
// Equality predicates
Predicate<String> equalToHello = PredicateUtils.equalPredicate("hello");
Predicate<Integer> equalToTen = PredicateUtils.equalPredicate(10);
// Instance type predicate
Predicate<Object> stringInstancePred = PredicateUtils.instanceofPredicate(String.class);
Predicate<Object> numberInstancePred = PredicateUtils.instanceofPredicate(Number.class);
// Test objects
List<Object> mixed = Arrays.asList("hello", 42, null, "world", 3.14, null);
Collection<Object> notNulls = CollectionUtils.select(mixed, notNullPred);
// Result: ["hello", 42, "world", 3.14]
Collection<Object> strings = CollectionUtils.select(mixed, stringInstancePred);
// Result: ["hello", "world"]// Logical operations
Predicate<Integer> positive = n -> n > 0;
Predicate<Integer> lessThanTen = n -> n < 10;
Predicate<Integer> even = n -> n % 2 == 0;
// AND combination
Predicate<Integer> positiveAndEven = PredicateUtils.andPredicate(positive, even);
Predicate<Integer> singleDigitPositive = PredicateUtils.andPredicate(positive, lessThanTen);
// OR combination
Predicate<Integer> positiveOrEven = PredicateUtils.orPredicate(positive, even);
// NOT operation
Predicate<Integer> notEven = PredicateUtils.notPredicate(even); // Odd numbers
// Complex combinations
Predicate<Integer> complexPredicate = PredicateUtils.andPredicate(
positiveAndEven,
lessThanTen
); // Positive, even, single-digit numbers
List<Integer> numbers = Arrays.asList(-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
Collection<Integer> positiveEvenSingleDigit = CollectionUtils.select(numbers, complexPredicate);
// Result: [2, 4, 6, 8]// Only accepts each object once
Predicate<String> uniquePred = PredicateUtils.uniquePredicate();
List<String> withDuplicates = Arrays.asList("a", "b", "a", "c", "b", "d", "a");
Collection<String> unique = CollectionUtils.select(withDuplicates, uniquePred);
// Result: ["a", "b", "c", "d"] (first occurrence of each)
// Reset by creating new predicate
Predicate<String> anotherUnique = PredicateUtils.uniquePredicate();
// This one has fresh state// Invoke method and test result
Predicate<String> isEmptyPred = PredicateUtils.invokerPredicate("isEmpty");
Predicate<List<?>> isListEmptyPred = PredicateUtils.invokerPredicate("isEmpty");
List<String> testStrings = Arrays.asList("", "hello", "", "world", "");
Collection<String> emptyStrings = CollectionUtils.select(testStrings, isEmptyPred);
// Result: ["", "", ""]
// Method with parameters
Predicate<String> startsWithHello = PredicateUtils.invokerPredicate(
"startsWith",
new Class[]{String.class},
new Object[]{"hello"}
);import org.apache.commons.collections4.TransformerUtils;
// Identity transformer (returns input unchanged)
Transformer<String, String> identity = TransformerUtils.nopTransformer();
// Constant transformer (always returns same value)
Transformer<Object, String> constant = TransformerUtils.constantTransformer("constant");
// Null transformer (always returns null)
Transformer<Object, Object> nullTransformer = TransformerUtils.nullTransformer();
// Class transformer (returns object's class)
Transformer<Object, Class<?>> classTransformer = TransformerUtils.asTransformer(Object.class);
// String representation
Transformer<Object, String> stringTransformer = TransformerUtils.stringValueTransformer();
List<Object> objects = Arrays.asList(42, "hello", 3.14, true);
Collection<String> strings = CollectionUtils.collect(objects, stringTransformer);
// Result: ["42", "hello", "3.14", "true"]
Collection<Class<?>> classes = CollectionUtils.collect(objects, classTransformer);
// Result: [Integer.class, String.class, Double.class, Boolean.class]// Chain multiple transformers
Transformer<String, String> trim = String::trim;
Transformer<String, String> upperCase = String::toUpperCase;
Transformer<String, Integer> length = String::length;
// Chain string operations
Transformer<String, String> trimAndUpper = TransformerUtils.chainedTransformer(trim, upperCase);
Transformer<String, Integer> trimUpperLength = TransformerUtils.chainedTransformer(
trim, upperCase, length
);
List<String> messy = Arrays.asList(" hello ", " WORLD ", " Java ");
Collection<String> cleaned = CollectionUtils.collect(messy, trimAndUpper);
// Result: ["HELLO", "WORLD", "JAVA"]
Collection<Integer> lengths = CollectionUtils.collect(messy, trimUpperLength);
// Result: [5, 5, 4]// Transform using lookup map
Map<String, String> countryToCapital = Map.of(
"USA", "Washington D.C.",
"France", "Paris",
"Japan", "Tokyo",
"Germany", "Berlin"
);
Transformer<String, String> capitalLookup = TransformerUtils.mapTransformer(countryToCapital);
List<String> countries = Arrays.asList("USA", "France", "Japan");
Collection<String> capitals = CollectionUtils.collect(countries, capitalLookup);
// Result: ["Washington D.C.", "Paris", "Tokyo"]// If-then-else transformer
Predicate<Integer> isPositive = n -> n > 0;
Transformer<Integer, String> positiveTransform = n -> "positive: " + n;
Transformer<Integer, String> negativeTransform = n -> "negative: " + n;
Transformer<Integer, String> conditionalTransformer = TransformerUtils.ifTransformer(
isPositive,
positiveTransform,
negativeTransform
);
List<Integer> numbers = Arrays.asList(-5, -1, 0, 1, 5);
Collection<String> results = CollectionUtils.collect(numbers, conditionalTransformer);
// Result: ["negative: -5", "negative: -1", "negative: 0", "positive: 1", "positive: 5"]
// Switch transformer with multiple conditions
Predicate<Integer> isZero = n -> n == 0;
Predicate<Integer> isPositive2 = n -> n > 0;
Transformer<Integer, String> zeroTransform = n -> "zero";
Transformer<Integer, String> defaultTransform = n -> "unknown";
Transformer<Integer, String> switchTransformer = TransformerUtils.switchTransformer(
new Predicate[]{isZero, isPositive2},
new Transformer[]{zeroTransform, positiveTransform},
defaultTransform
);import org.apache.commons.collections4.ClosureUtils;
// No-operation closure (does nothing)
Closure<Object> nopClosure = ClosureUtils.nopClosure();
// Exception closure (always throws exception)
Closure<Object> exceptionClosure = ClosureUtils.exceptionClosure();
// Invoker closure (calls method on object)
Closure<StringBuilder> appendClosure = ClosureUtils.invokerClosure("append",
new Class[]{String.class}, new Object[]{" - processed"});
List<StringBuilder> builders = Arrays.asList(
new StringBuilder("item1"),
new StringBuilder("item2"),
new StringBuilder("item3")
);
IterableUtils.forEach(builders, appendClosure);
// Each StringBuilder now has " - processed" appended// Chain multiple closures
Closure<List<String>> addHello = list -> list.add("Hello");
Closure<List<String>> addWorld = list -> list.add("World");
Closure<List<String>> printSize = list -> System.out.println("Size: " + list.size());
Closure<List<String>> chainedClosure = ClosureUtils.chainedClosure(
addHello, addWorld, printSize
);
List<String> testList = new ArrayList<>();
chainedClosure.execute(testList);
// testList now contains ["Hello", "World"] and prints "Size: 2"// Execute closure N times
Closure<StringBuilder> appendStar = sb -> sb.append("*");
Closure<StringBuilder> repeatStar = ClosureUtils.forClosure(5, appendStar);
StringBuilder sb = new StringBuilder();
repeatStar.execute(sb);
System.out.println(sb.toString()); // "*****"
// While closure (execute while condition is true)
class Counter {
private int count = 0;
public int getCount() { return count; }
public void increment() { count++; }
}
Counter counter = new Counter();
Predicate<Counter> lessThanFive = c -> c.getCount() < 5;
Closure<Counter> increment = Counter::increment;
Closure<Counter> whileClosure = ClosureUtils.whileClosure(lessThanFive, increment);
whileClosure.execute(counter);
System.out.println(counter.getCount()); // 5import org.apache.commons.collections4.FactoryUtils;
import java.util.Date;
// Constant factory
Factory<String> constantStringFactory = FactoryUtils.constantFactory("default");
// Null factory
Factory<Object> nullFactory = FactoryUtils.nullFactory();
// Exception factory
Factory<Object> exceptionFactory = FactoryUtils.exceptionFactory();
// Prototype factory (clones prototype)
List<String> prototype = Arrays.asList("template", "item");
Factory<List<String>> prototypeFactory = FactoryUtils.prototypeFactory(prototype);
List<String> copy1 = prototypeFactory.create(); // Independent copy
List<String> copy2 = prototypeFactory.create(); // Another independent copy
// Instantiate factory (creates new instances via constructor)
Factory<Date> dateFactory = FactoryUtils.instantiateFactory(Date.class);
Date now1 = dateFactory.create(); // new Date()
Date now2 = dateFactory.create(); // another new Date()
// Instantiate with constructor parameters
Factory<StringBuilder> sbFactory = FactoryUtils.instantiateFactory(
StringBuilder.class,
new Class[]{String.class},
new Object[]{"initial"}
);
StringBuilder sb1 = sbFactory.create(); // new StringBuilder("initial")public class DataProcessor {
// Define processing pipeline with functors
private final Predicate<String> validator = s -> s != null && !s.trim().isEmpty();
private final Transformer<String, String> cleaner = s -> s.trim().toLowerCase();
private final Transformer<String, String> formatter = s -> "processed: " + s;
private final Closure<String> logger = s -> System.out.println("Processed: " + s);
public List<String> processData(List<String> rawData) {
return rawData.stream()
.filter(validator::evaluate) // Validate
.map(cleaner::transform) // Clean
.map(formatter::transform) // Format
.peek(logger::execute) // Log (side effect)
.collect(Collectors.toList());
}
}public class CommandProcessor {
private final Map<String, Closure<String>> commands = new HashMap<>();
public CommandProcessor() {
// Register commands as closures
commands.put("uppercase", String::toUpperCase);
commands.put("print", System.out::println);
commands.put("reverse", s -> new StringBuilder(s).reverse().toString());
}
public void executeCommand(String commandName, String input) {
Closure<String> command = commands.get(commandName);
if (command != null) {
command.execute(input);
}
}
public void registerCommand(String name, Closure<String> command) {
commands.put(name, command);
}
}public class TextProcessor {
public enum ProcessingStrategy {
UPPERCASE(String::toUpperCase),
LOWERCASE(String::toLowerCase),
CAPITALIZE(s -> s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase()),
REVERSE(s -> new StringBuilder(s).reverse().toString());
private final Transformer<String, String> transformer;
ProcessingStrategy(Transformer<String, String> transformer) {
this.transformer = transformer;
}
public String process(String input) {
return transformer.transform(input);
}
}
public List<String> processTexts(List<String> texts, ProcessingStrategy strategy) {
return CollectionUtils.collect(texts, strategy.transformer);
}
}public class ValidationFramework {
public static class ValidationResult {
private final boolean valid;
private final List<String> errors;
// Constructor and methods...
}
public static <T> ValidationResult validate(T object, Predicate<T>... validators) {
List<String> errors = new ArrayList<>();
for (int i = 0; i < validators.length; i++) {
if (!validators[i].evaluate(object)) {
errors.add("Validation " + i + " failed");
}
}
return new ValidationResult(errors.isEmpty(), errors);
}
// Usage example
public ValidationResult validateUser(User user) {
return validate(user,
u -> u.getName() != null && !u.getName().trim().isEmpty(), // Name required
u -> u.getAge() >= 18, // Adult
u -> u.getEmail() != null && u.getEmail().contains("@") // Valid email
);
}
}// Efficient: short-circuit evaluation
Predicate<String> efficientPredicate = PredicateUtils.andPredicate(
s -> s != null, // Fast null check first
s -> s.length() > 10, // Then length check
s -> s.contains("expensive") // Most expensive check last
);
// Less efficient: expensive operations first
Predicate<String> inefficientPredicate = PredicateUtils.andPredicate(
s -> expensiveOperation(s), // Expensive operation first
s -> s != null // Null check last (may never execute)
);// Create reusable functors to avoid recreation overhead
public class CommonFunctors {
public static final Predicate<String> NOT_EMPTY =
s -> s != null && !s.trim().isEmpty();
public static final Transformer<String, String> TRIM_UPPERCASE =
s -> s == null ? null : s.trim().toUpperCase();
public static final Factory<List<String>> STRING_LIST_FACTORY =
ArrayList::new;
// Use in multiple contexts
public static Collection<String> cleanStrings(Collection<String> input) {
return CollectionUtils.collect(
CollectionUtils.select(input, NOT_EMPTY),
TRIM_UPPERCASE
);
}
}// Memory-efficient: process items one at a time
public void processLargeDataset(Iterable<String> largeDataset) {
Predicate<String> filter = s -> s.startsWith("important");
Transformer<String, String> transform = String::toUpperCase;
Closure<String> process = this::processItem;
// Memory-efficient streaming approach
for (String item : largeDataset) {
if (filter.evaluate(item)) {
String transformed = transform.transform(item);
process.execute(transformed);
}
}
}
// Less memory-efficient: creates intermediate collections
public void processLargeDatasetInefficient(Collection<String> largeDataset) {
Collection<String> filtered = CollectionUtils.select(largeDataset, filter); // Full collection in memory
Collection<String> transformed = CollectionUtils.collect(filtered, transform); // Another full collection
IterableUtils.forEach(transformed, process); // Process all at once
}// Use specific generic types
Predicate<String> stringPredicate = s -> s.length() > 0; // Good
Predicate<Object> objectPredicate = o -> o.toString().length() > 0; // Less safe
// Avoid raw types
Predicate rawPredicate = s -> true; // Don't do this// Safe predicate that handles nulls
Predicate<String> safeStringPredicate = s -> {
try {
return s != null && s.trim().length() > 0;
} catch (Exception e) {
return false; // Safe default
}
};
// Safe transformer with error handling
Transformer<String, Integer> safeParseInt = s -> {
try {
return s != null ? Integer.parseInt(s.trim()) : 0;
} catch (NumberFormatException e) {
return 0; // Safe default
}
};Functional programming support in Apache Commons Collections enables powerful, composable operations on collections while maintaining type safety and performance. Use these patterns to create clean, reusable, and maintainable code.
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-commons--commons-collections4