Small Java utilities that fill gaps in the standard library. No heavy dependencies, no magic.
This library collects small Java utilities that come up repeatedly across projects at
cronn. Some of them, like StreamUtil.toLinkedHashSet() and
SetUtils.orderedSet(...), directly reflect our stance on
determinism: prefer
ordered collections by default so that behaviour is consistent across runs, JVM versions, and
environments, even when order is not required for correctness.
Gradle (Kotlin DSL)
implementation("de.cronn:commons-lang:1.6")Maven
<dependency>
<groupId>de.cronn</groupId>
<artifactId>commons-lang</artifactId>
<version>1.6</version>
</dependency>| Class | Description |
|---|---|
StreamUtil |
Collectors and stream utilities missing from the JDK |
SetUtils |
Factory methods for ordered sets |
Action |
Runnable that allows throwing checked exceptions |
AlphanumericComparator |
Human-friendly sorting of strings with embedded numbers |
StreamUtil provides collectors and stream utilities that complement java.util.stream.Collectors.
It covers common patterns like collecting to a single element, deduplication by key, and
order-preserving alternatives to standard collectors.
Collects exactly one element, throws IllegalStateException otherwise.
// works fine
int number = Stream.of(1, 2, 3)
.filter(value -> value > 2)
.collect(StreamUtil.toSingleElement());
// number = 3
// throws: Exactly one element expected but got 2: [3, 4]
Stream.of(1, 2, 3, 4)
.filter(value -> value > 2)
.collect(StreamUtil.toSingleElement());Like toSingleElement(), but returns an Optional instead of throwing when no element is found.
Optional<Integer> number = Stream.of(1, 2, 3)
.filter(value -> value > 2)
.collect(StreamUtil.toSingleOptionalElement());Drop-in replacement for Collectors.toSet() with a guaranteed, stable iteration order.
SequencedSet<Integer> numbers = Stream.of(1, 2, 3, 2, 3)
.collect(StreamUtil.toLinkedHashSet());
// iteration order: 1, 2, 3Checks whether a stream contains any duplicate elements. Short-circuits on the first duplicate found.
StreamUtil.hasDuplicates(Stream.of(1, 2, 3)); // false
StreamUtil.hasDuplicates(Stream.of(1, 2, 1, 3)); // trueAn overload accepts a Comparator for custom equality semantics, e.g. case-insensitive string comparison:
StreamUtil.hasDuplicates(Stream.of("Hello", "world", "HELLO"), String.CASE_INSENSITIVE_ORDER); // true
StreamUtil.hasDuplicates(Stream.of("a", "A"), Comparator.naturalOrder()); // false
StreamUtil.hasDuplicates(Stream.of("a", "A"), String.CASE_INSENSITIVE_ORDER); // trueA stateful Predicate for Stream.filter() that keeps only the first element per distinct key.
Unlike Stream.distinct(), deduplication is based on an extracted key rather than the element itself.
List<String> result = Stream.of("one", "two", "three", "four")
.filter(StreamUtil.distinctByKey(s -> s.charAt(0)))
.toList();
// result = ["one", "two", "four"]An optional Consumer overload lets you capture the duplicates that were filtered out:
List<String> duplicates = new ArrayList<>();
List<String> result = Stream.of("one", "two", "TWO", "three", "Three", "four")
.filter(StreamUtil.distinctByKey(String::toLowerCase, duplicates::add))
.toList();
// result = ["one", "two", "three", "four"]
// duplicates = ["TWO", "Three"]Factory methods for ordered sets. Unlike Set.of(…), SetUtils.orderedSet(…) preserves insertion
order, silently drops duplicates, and returns a mutable set.
SequencedSet<String> ordered = SetUtils.orderedSet("abc", "def", "ghi");
// iteration order: abc, def, ghi
SequencedSet<Integer> numbers = SetUtils.orderedSet(3, 1, 2, 1);
// iteration order: 3, 1, 2 (duplicate 1 dropped)A functional interface like Runnable, but allowed to throw checked exceptions. Useful as a
lambda handle for any void operation that may fail, with adapters to standard JDK types.
Action action = () -> Files.delete(path);
// wrap checked exceptions as RuntimeException
Supplier<Void> supplier = action.toSupplier();
// propagate checked exceptions unchanged
Callable<Void> callable = action.toCallable();Humans sort
file2.txtbeforefile10.txt. Computers don't, unless you tell them to.
AlphanumericComparator splits strings into text and numeric segments and compares numeric parts
by value, producing the natural order people expect.
| Lexicographic order | Alphanumeric order |
|---|---|
| file1.txt | file1.txt |
| file10.txt | file2.txt |
| file2.txt | file3.txt |
| file3.txt | file10.txt |
List<String> files = List.of("file10.txt", "file3.txt", "file1.txt", "file2.txt");
files.stream()
.sorted(AlphanumericComparator.getInstance())
.toList();
// [file1.txt, file2.txt, file3.txt, file10.txt]Convenience predicates are also available:
AlphanumericComparator.isBefore("file2.txt", "file10.txt"); // true
AlphanumericComparator.isAfter("file10.txt", "file3.txt"); // true
AlphanumericComparator.isAfterOrEqual("file3.txt", "file3.txt"); // true- Java 21+