The Apache Commons Collections package contains types that extend and augment the Java Collections Framework
—
Apache Commons Collections provides several specialized collection types for specific use cases including tries (prefix trees), bloom filters, advanced queues, and sophisticated iterators.
Tries are tree-based data structures optimized for prefix-based operations on strings or sequences.
import org.apache.commons.collections4.Trie;
import org.apache.commons.collections4.trie.PatriciaTrie;
import java.util.SortedMap;
Trie<String, Integer> trie = new PatriciaTrie<>();
// Add key-value pairs
trie.put("cat", 1);
trie.put("car", 2);
trie.put("card", 3);
trie.put("care", 4);
trie.put("careful", 5);
trie.put("dog", 6);
// Standard map operations
Integer value = trie.get("car"); // Returns 2
boolean hasKey = trie.containsKey("card"); // true
int size = trie.size(); // 6
// Prefix-based operations
SortedMap<String, Integer> carPrefixed = trie.prefixMap("car");
// Returns: {car=2, card=3, care=4, careful=5}
SortedMap<String, Integer> cPrefixed = trie.prefixMap("c");
// Returns: {car=2, card=3, care=4, careful=5, cat=1}
// Empty prefix returns all entries
SortedMap<String, Integer> all = trie.prefixMap("");PATRICIA (Practical Algorithm to Retrieve Information Coded in Alphanumeric) trie implementation.
import org.apache.commons.collections4.trie.PatriciaTrie;
import java.util.Map;
// Create for String keys
PatriciaTrie<String> dictionary = new PatriciaTrie<>();
// Add dictionary entries
dictionary.put("apple", "A red or green fruit");
dictionary.put("application", "A software program");
dictionary.put("apply", "To put to use");
dictionary.put("appreciate", "To value or understand");
// Autocomplete functionality
SortedMap<String, String> suggestions = dictionary.prefixMap("app");
System.out.println("Words starting with 'app':");
suggestions.forEach((word, definition) ->
System.out.println(word + ": " + definition));
// Create from existing map
Map<String, Integer> wordCounts = Map.of(
"hello", 5,
"world", 3,
"help", 2,
"helper", 1
);
PatriciaTrie<Integer> countTrie = new PatriciaTrie<>(wordCounts);
// Find all words with "hel" prefix
SortedMap<String, Integer> helWords = countTrie.prefixMap("hel");
// Returns: {hello=5, help=2, helper=1}public class AutocompleteSystem {
private final Trie<String, Integer> trie = new PatriciaTrie<>();
public void addWord(String word, int frequency) {
trie.put(word.toLowerCase(), frequency);
}
public List<String> getSuggestions(String prefix, int maxResults) {
SortedMap<String, Integer> matches = trie.prefixMap(prefix.toLowerCase());
return matches.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) // Sort by frequency
.limit(maxResults)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
public boolean hasWord(String word) {
return trie.containsKey(word.toLowerCase());
}
public void incrementFrequency(String word) {
String key = word.toLowerCase();
int currentFreq = trie.getOrDefault(key, 0);
trie.put(key, currentFreq + 1);
}
}public class PhoneDirectory {
private final Trie<String, Contact> byName = new PatriciaTrie<>();
private final Trie<String, Contact> byPhone = new PatriciaTrie<>();
public void addContact(Contact contact) {
byName.put(contact.getName().toLowerCase(), contact);
byPhone.put(contact.getPhone(), contact);
}
public List<Contact> searchByName(String namePrefix) {
return new ArrayList<>(byName.prefixMap(namePrefix.toLowerCase()).values());
}
public List<Contact> searchByPhone(String phonePrefix) {
return new ArrayList<>(byPhone.prefixMap(phonePrefix).values());
}
public Contact getExactMatch(String name) {
return byName.get(name.toLowerCase());
}
}Probabilistic data structures for membership testing with no false negatives but possible false positives.
import org.apache.commons.collections4.bloomfilter.BloomFilter;
import org.apache.commons.collections4.bloomfilter.SimpleBloomFilter;
import org.apache.commons.collections4.bloomfilter.Shape;
import org.apache.commons.collections4.bloomfilter.Hasher;
import org.apache.commons.collections4.bloomfilter.EnhancedDoubleHasher;
// Create bloom filter shape (size and hash functions)
Shape shape = Shape.fromEstimates(1000, 0.01); // Expected elements: 1000, false positive rate: 1%
BloomFilter filter = new SimpleBloomFilter(shape);
// Add elements (using hashers)
Hasher hasher1 = new EnhancedDoubleHasher("element1".hashCode(), "element1".hashCode());
Hasher hasher2 = new EnhancedDoubleHasher("element2".hashCode(), "element2".hashCode());
filter.merge(hasher1);
filter.merge(hasher2);
// Test membership
boolean mayContain1 = filter.contains(hasher1); // true (definitely in set)
Hasher hasher3 = new EnhancedDoubleHasher("element3".hashCode(), "element3".hashCode());
boolean mayContain3 = filter.contains(hasher3); // false or true (maybe in set)
// Get filter statistics
int cardinality = filter.cardinality(); // Number of set bits
Shape filterShape = filter.getShape(); // Configuration detailsAllows removal of elements by maintaining counts.
import org.apache.commons.collections4.bloomfilter.CountingBloomFilter;
CountingBloomFilter countingFilter = new ArrayCountingBloomFilter(shape);
// Add elements
countingFilter.merge(hasher1);
countingFilter.merge(hasher1); // Add same element twice
// Remove elements
boolean removed = countingFilter.remove(hasher1); // Decrements count
boolean removedAgain = countingFilter.remove(hasher1); // Decrements to zero
// Element no longer in filter
boolean stillThere = countingFilter.contains(hasher1); // falsepublic class CacheWithBloomFilter<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final BloomFilter filter;
private final Function<K, Hasher> hasherFunction;
public CacheWithBloomFilter(Shape shape, Function<K, Hasher> hasherFunction) {
this.filter = new SimpleBloomFilter(shape);
this.hasherFunction = hasherFunction;
}
public void put(K key, V value) {
cache.put(key, value);
filter.merge(hasherFunction.apply(key));
}
public V get(K key) {
// Fast negative check - if not in bloom filter, definitely not in cache
if (!filter.contains(hasherFunction.apply(key))) {
return null; // Definitely not in cache
}
// May or may not be in cache - check actual cache
return cache.get(key);
}
public boolean mightContain(K key) {
return filter.contains(hasherFunction.apply(key));
}
}Apache Commons Collections provides several specialized bloom filter implementations for different use cases.
import org.apache.commons.collections4.bloomfilter.ArrayCountingBloomFilter;
import org.apache.commons.collections4.bloomfilter.BitMapBloomFilter;
import org.apache.commons.collections4.bloomfilter.LayeredBloomFilter;
// Array-based counting bloom filter (supports removal)
ArrayCountingBloomFilter countingFilter = new ArrayCountingBloomFilter(shape);
countingFilter.merge(hasher1);
countingFilter.merge(hasher1); // Add twice
boolean removed = countingFilter.remove(hasher1); // Remove once
// Bitmap-based bloom filter for memory efficiency
BitMapBloomFilter bitMapFilter = new BitMapBloomFilter(shape);
bitMapFilter.merge(hasher1);
boolean contains = bitMapFilter.contains(hasher1); // Fast membership test
// Layered bloom filter for time-based data
LayeredBloomFilter layeredFilter = new LayeredBloomFilter(shape, 3); // 3 layers
layeredFilter.merge(hasher1);
layeredFilter.next(); // Advance to next layer
layeredFilter.merge(hasher2);The Shape class configures bloom filter parameters for optimal performance.
import org.apache.commons.collections4.bloomfilter.Shape;
// Create shape from estimates
Shape fromEstimates = Shape.fromEstimates(1000, 0.01); // 1000 items, 1% false positive rate
// Create shape from parameters
Shape fromParams = Shape.fromNP(1000, 10); // 1000 items, 10 hash functions
// Get shape properties
int numberOfBits = fromEstimates.getNumberOfBits(); // Total bits in filter
int numberOfHashFunctions = fromEstimates.getNumberOfHashFunctions(); // Hash functions used
double probability = fromEstimates.getProbability(500); // False positive rate at 500 items
// Calculate optimal parameters for different scenarios
int optimalBits = Shape.calculateNumberOfBits(1000, 0.01); // Bits needed
int optimalHashes = Shape.calculateNumberOfHashFunctions(1000, 9600); // Hash functions neededA fixed-size queue that overwrites the oldest element when full.
import org.apache.commons.collections4.queue.CircularFifoQueue;
// Create with default size (32)
CircularFifoQueue<String> defaultQueue = new CircularFifoQueue<>();
// Create with specific size
CircularFifoQueue<Integer> numbers = new CircularFifoQueue<>(5);
// Add elements
numbers.offer(1);
numbers.offer(2);
numbers.offer(3);
numbers.offer(4);
numbers.offer(5);
System.out.println(numbers); // [1, 2, 3, 4, 5]
// Adding when full overwrites oldest
numbers.offer(6); // Overwrites 1
numbers.offer(7); // Overwrites 2
System.out.println(numbers); // [3, 4, 5, 6, 7]
// Queue properties
boolean isFull = numbers.isFull(); // true
int maxSize = numbers.maxSize(); // 5
int currentSize = numbers.size(); // 5
// Create from existing collection (takes only last N elements if oversized)
List<String> manyItems = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
CircularFifoQueue<String> fromList = new CircularFifoQueue<>(manyItems); // Takes last 32
CircularFifoQueue<String> fromListSized = new CircularFifoQueue<>(3, manyItems); // Takes "f", "g", "h"import org.apache.commons.collections4.QueueUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.Transformer;
import java.util.LinkedList;
import java.util.Queue;
Queue<String> baseQueue = new LinkedList<>();
// Synchronized queue
Queue<String> syncQueue = QueueUtils.synchronizedQueue(baseQueue);
// Unmodifiable queue
baseQueue.offer("item1");
baseQueue.offer("item2");
Queue<String> readOnlyQueue = QueueUtils.unmodifiableQueue(baseQueue);
// Predicated queue (validates additions)
Predicate<String> nonEmpty = s -> s != null && !s.trim().isEmpty();
Queue<String> validatedQueue = QueueUtils.predicatedQueue(new LinkedList<>(), nonEmpty);
validatedQueue.offer("valid"); // Success
try {
validatedQueue.offer(""); // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
System.out.println("Empty string rejected");
}
// Transforming queue
Transformer<String, String> upperCase = String::toUpperCase;
Queue<String> transformingQueue = QueueUtils.transformingQueue(new LinkedList<>(), upperCase);
transformingQueue.offer("hello"); // Stored as "HELLO"public class RecentItemsCache<T> {
private final CircularFifoQueue<T> recentItems;
private final Set<T> itemSet = new LinkedHashSet<>();
public RecentItemsCache(int maxSize) {
this.recentItems = new CircularFifoQueue<>(maxSize);
}
public void addItem(T item) {
// Remove if already exists to avoid duplicates
if (itemSet.contains(item)) {
recentItems.remove(item);
itemSet.remove(item);
}
// Add new item
if (recentItems.isFull()) {
T removed = recentItems.poll();
itemSet.remove(removed);
}
recentItems.offer(item);
itemSet.add(item);
}
public List<T> getRecentItems() {
return new ArrayList<>(recentItems);
}
public boolean isRecent(T item) {
return itemSet.contains(item);
}
}Iterator that supports bidirectional navigation.
import org.apache.commons.collections4.OrderedIterator;
import org.apache.commons.collections4.iterators.ListIteratorWrapper;
import java.util.List;
import java.util.Arrays;
List<String> list = Arrays.asList("first", "second", "third", "fourth");
OrderedIterator<String> iterator = new ListIteratorWrapper<>(list.listIterator());
// Forward iteration
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println("Forward: " + item);
}
// Backward iteration
while (iterator.hasPrevious()) {
String item = iterator.previous();
System.out.println("Backward: " + item);
}Iterator that can be reset to its initial position.
import org.apache.commons.collections4.ResettableIterator;
import org.apache.commons.collections4.iterators.ArrayIterator;
String[] array = {"a", "b", "c", "d"};
ResettableIterator<String> resetIterator = new ArrayIterator<>(array);
// First iteration
while (resetIterator.hasNext()) {
System.out.println("First pass: " + resetIterator.next());
}
// Reset and iterate again
resetIterator.reset();
while (resetIterator.hasNext()) {
System.out.println("Second pass: " + resetIterator.next());
}Iterator optimized for map traversal.
import org.apache.commons.collections4.MapIterator;
import org.apache.commons.collections4.map.LinkedMap;
LinkedMap<String, Integer> map = new LinkedMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 8);
MapIterator<String, Integer> mapIterator = map.mapIterator();
while (mapIterator.hasNext()) {
String key = mapIterator.next();
Integer value = mapIterator.getValue();
// Modify value in-place
mapIterator.setValue(value * 2);
System.out.println(key + " -> " + mapIterator.getValue());
}import org.apache.commons.collections4.IteratorUtils;
import java.util.Iterator;
import java.util.Arrays;
List<String> list1 = Arrays.asList("a", "b", "c");
List<String> list2 = Arrays.asList("d", "e", "f");
List<String> list3 = Arrays.asList("g", "h", "i");
// Chain multiple iterators
Iterator<String> chained = IteratorUtils.chainedIterator(
list1.iterator(),
list2.iterator(),
list3.iterator()
);
// Iterate through all elements in sequence
while (chained.hasNext()) {
System.out.println(chained.next()); // a, b, c, d, e, f, g, h, i
}import org.apache.commons.collections4.Predicate;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter even numbers
Predicate<Integer> evenPredicate = n -> n % 2 == 0;
Iterator<Integer> evenNumbers = IteratorUtils.filteredIterator(numbers.iterator(), evenPredicate);
System.out.println("Even numbers:");
while (evenNumbers.hasNext()) {
System.out.println(evenNumbers.next()); // 2, 4, 6, 8, 10
}import org.apache.commons.collections4.Transformer;
List<String> words = Arrays.asList("hello", "world", "java", "collections");
// Transform to uppercase
Transformer<String, String> upperCase = String::toUpperCase;
Iterator<String> upperCaseIterator = IteratorUtils.transformedIterator(words.iterator(), upperCase);
System.out.println("Uppercase words:");
while (upperCaseIterator.hasNext()) {
System.out.println(upperCaseIterator.next()); // HELLO, WORLD, JAVA, COLLECTIONS
}
// Transform to word lengths
Transformer<String, Integer> lengthTransformer = String::length;
Iterator<Integer> lengthIterator = IteratorUtils.transformedIterator(words.iterator(), lengthTransformer);Merges multiple sorted iterators into a single sorted iterator.
import java.util.Comparator;
List<Integer> list1 = Arrays.asList(1, 4, 7, 10); // Sorted
List<Integer> list2 = Arrays.asList(2, 5, 8, 11); // Sorted
List<Integer> list3 = Arrays.asList(3, 6, 9, 12); // Sorted
// Merge into single sorted sequence
Iterator<Integer> collated = IteratorUtils.collatingIterator(
Comparator.naturalOrder(),
list1.iterator(),
list2.iterator(),
list3.iterator()
);
System.out.println("Merged sorted sequence:");
while (collated.hasNext()) {
System.out.println(collated.next()); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
}// Trie operations are O(k) where k is key length, not collection size
Trie<String, Object> largeTrie = new PatriciaTrie<>();
// Adding 1M entries - each operation is O(key_length)
for (int i = 0; i < 1_000_000; i++) {
largeTrie.put("key" + i, i);
}
// Prefix search is still O(prefix_length + result_size)
SortedMap<String, Object> results = largeTrie.prefixMap("key123"); // Fast even with 1M entries// CircularFifoQueue offers O(1) operations
CircularFifoQueue<String> efficient = new CircularFifoQueue<>(1000);
// All operations are constant time
efficient.offer("item"); // O(1)
String item = efficient.poll(); // O(1)
boolean full = efficient.isFull(); // O(1)
// Much more efficient than removing from front of ArrayList
List<String> inefficient = new ArrayList<>();
// Adding to end: O(1), removing from front: O(n)// Memory-efficient iteration over large collections
List<String> hugeList = generateHugeList(); // Millions of items
// Memory efficient - only holds current position
Iterator<String> memoryEfficient = hugeList.iterator();
// Less memory efficient - may hold references to filtered items
List<String> filtered = hugeList.stream()
.filter(s -> s.startsWith("prefix"))
.collect(Collectors.toList());
// Use iterator utilities for memory-efficient processing
Iterator<String> filteredIterator = IteratorUtils.filteredIterator(
hugeList.iterator(),
s -> s.startsWith("prefix")
);// Use Trie for prefix-based operations
Trie<String, Object> autocomplete = new PatriciaTrie<>(); // Autocomplete, dictionaries
// Use Bloom Filter for membership pre-filtering
BloomFilter membershipFilter = new SimpleBloomFilter(shape); // Cache layers, deduplication
// Use CircularFifoQueue for fixed-size buffers
CircularFifoQueue<LogEntry> logBuffer = new CircularFifoQueue<>(1000); // Recent items, logs
// Use specialized iterators for complex traversal
Iterator<String> complexTraversal = IteratorUtils.chainedIterator(
IteratorUtils.filteredIterator(source1.iterator(), predicate1),
IteratorUtils.transformedIterator(source2.iterator(), transformer)
);// Safe trie operations
public List<String> safeGetSuggestions(Trie<String, Integer> trie, String prefix) {
if (prefix == null || trie == null) {
return Collections.emptyList();
}
try {
SortedMap<String, Integer> matches = trie.prefixMap(prefix);
return new ArrayList<>(matches.keySet());
} catch (Exception e) {
// Log error and return empty results
return Collections.emptyList();
}
}
// Safe queue operations
public boolean safeOffer(CircularFifoQueue<String> queue, String item) {
if (queue == null || item == null) {
return false;
}
return queue.offer(item); // Always succeeds for CircularFifoQueue
}Advanced collections in Apache Commons Collections provide specialized data structures for specific performance and functionality requirements. Choose the appropriate type based on your access patterns, memory constraints, and performance needs.
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-commons--commons-collections4