slides.md
March 30, 2026 · View on GitHub
API Design: Eclipse Collections
- space: next page
- down arrow: next page in the current section
- right arrow: next section
Agenda
- What is Eclipse Collections?
- Design Goals
- Symmetric Sympathy
- Strategies for evolving a Collections API
- Memory Efficiency
- Performance Optimization Strategies
- New Features in Eclipse Collections 9.x
- How did we get here?
- The Past and the Future
What is Eclipse Collections?
- An open source Java Collections Framework
- Functional, Fluent, Friendly and Fun
- A mature library with an evolutionary design
- 18+ years of development
- A Brief History
- Caramel (2005)
- Internal library developed at Goldman Sachs
- GS Collections (2012)
- First open source project from Goldman Sachs
- Eclipse Collections (2015)
- Moved to the Eclipse Foundation
- Caramel (2005)
Project Information
- Overview
- Project Leads and Committers
Eclipse Collections Evangelism
- Conferences
- QCon New York, Devnexus, JavaOne, JavaDay Tokyo, Devoxx US, JCrete, EclipseCon, EclipseCon Europe, ECOOP, ScalaDays, GOTO Chicago, JVM Language Summit, GIDS
- Java User Group Meetups
- Americas - NY Java SIG, NY JUG, NJ Java SIG, Pittsburgh JUG, Utah JUG, Delaware JUG
- Europe - London Java Community, Dublin JUG, Belfast JUG, Edinburgh JUG, Manchester JC, West Midlands JUG
- Asia - JJUG CCC, Bengaluru JUG, Chennai JUG, Delhi-NCR JUG, Hyderabad JUG, Kerala JUG
Design Goals
- Rich “Functional, fluent and fun” API
- Eager, Lazy, Serial, Parallel
- Memory efficient data structures
- Optimized algorithms
- Java Interoperability
- Java container types, Streams
- Immutability
- Missing Types
- Intervals, Stacks, Bags, Multimaps, BiMaps
- Primitive Containers
Eclipse Collections Today
- Open for contribution at the Eclipse Foundation
- Symmetry drives the design
- Interoperability w/ Java 8 and Java 9

Symmetric Sympathy
- Blogs about Symmetry
Symmetry: Protocols and Interfaces
Symmetric Sympathy - Part 1
| API | Eager | Lazy | Parallel | Eager(p) | Lazy(p) |
|---|---|---|---|---|---|
select | co* | Lazy | co* | co* | Lazy |
reject | co* | Lazy | co* | co* | Lazy |
collect | co* | Lazy | co* | co* | Lazy |
detect | T | T | T | Primitive | Primitive |
injectInto | R | R | R | R | |
any/all/none | boolean | boolean | boolean | boolean | boolean |
toSet | mSet | mSet | mSet | m(p)Set | m(p)Set |
toBag | mBag | mBag | mSet | m(p)Bag | m(p)Bag |
toList | mList | mList | mList | m(p)List | m(p)List |
co*=covariant, m=mutable, p=primitive
Symmetric Sympathy - Part 2
| API | Eager | Lazy | Parallel | Eager(p) | Lazy(p) |
|---|---|---|---|---|---|
flatCollect | co* | Lazy | co* | R | Lazy |
groupBy | co* | Multimap | co* | ||
partition | co* | RI | |||
chunk | RI | RI | co* | Lazy | |
zip | co* | Lazy | co* | ||
makeString | String | String | String | String | String |
appendString | void | void | void | void | void |
count | int | int | int | int | int |
min/max | T | T | T | (p) | (p) |
co*=covariant, m=mutable, p=primitive
API Evolution Strategies
| Strategy | Cost | Complexity |
|---|---|---|
| Static Utility | Low | Low |
| Default methods | Low - Med | Medium |
| Lazy API | Med - High | Medium |
| Parallel Utility | Medium | Med - High |
| Eager API | High | Medium |
| New Data Type (Object) | High | Med - High |
| Parallel Lazy API | High | High |
| New Data Type (Primitive) | High | High |
Memory Efficiency
Memory Efficiency - Sets
Memory Efficiency - Immutable
Memory Efficiency - Primitives
Performance Optimization Strategies
Object vs. Primitive Performance
- Use Cases: Filter, Map, Sum, Filter/Map/Sum
- Data: 1 Million Ints
- Processor Name: 12-Core Intel Xeon E5
- Processor Speed: 2.7 GHz
- Number of Processors: 1
- Total Number of Cores: 12
- L2 Cache (per Core): 256 KB
- L3 Cache: 30 MB
- Memory: 64 GB
- Unit of Measure: Operations per second
Object vs. Primitive - Filter Evens

- Use case: Filter evens into new List
- Larger numbers are better
Object vs. Primitive - Map x 2

- Use case: Multiply each x 2 and return result in new List
- Larger numbers are better
Object vs. Primitive - Sum

- Use case: Sum all of the numbers
- Larger numbers are better
Object vs. Primitive - Filter, Map, Sum

- Use case: Filter evens, Map x 2, Sum
- Larger numbers are better
Eclipse Collections 10.4.0
- Mandatory fixes to make the library compatible with JDK-15.
Eclipse Collections 10.3.0 - (1)
- RichIterable
- ListIterable
- Bags
- MapIterable
- MutableMap
Eclipse Collections 10.3.0 - (2)
- MutablePrimitiveList
- PrimitiveIterable
- IntInterval
- Implemented singly-linked ImmutableStack.
- Implemented
LongInterval. - Implemented
Triple. - Implemented Tuples.identicalTwin(), Tuples.identicalTriplet().
- Implemented
withInitialCapacityin PrimitiveLists and PrimitiveSets
Eclipse Collections 10.2.0
- Exposed the
allocateTablemethod asprotectedin Primitive Maps and Primitive Sets.
Eclipse Collections 10.1.0
- RichIterable
- Implemented NoopProcedure.
Eclipse Collections 10.0.0 - (1)
- RichIterable
- MutableMapIterable
- Bag
- Multimap
Eclipse Collections 10.0.0 - (2)
- UnifiedSetWithHashingStrategy
- PrimitiveIterable
- PrimitiveList
- PrimitiveMap
- LazyIterate
- Added ability to create ObjectPrimitiveMap, PrimitiveObjectMap, PrimitivePrimitiveMap from Iterable.
- Implemented factory methods to convert Iterable
to PrimitiveList, PrimitiveSet, PrimitiveBag, PrimitiveStack. - Implemented fromStream(Stream) on Mutable Collection Factories and Immutable Collection Factories.
- Implemented ofInitialCapacity() and withInitialCapacity() to Primitive Map Factories and in HashingStrategySets.
Eclipse Collections 9.2
- RichIterable
- Bag
chunk(all primitive iterables)newEmpty(all mutable primitive collections)- Implemented
OrderedMapAdapter - Collectors2
MultiReaderfactories onSetsandBags
Eclipse Collections 9.1
collectWithIndexzip(primitive lists)removeIf(primitive collections)putPair(primitive maps)- Collectors2
MultiReaderListfactory onLists- New Factory class for
Strings
Eclipse Collections 9.0
- Java 9
- Automatic Module Naming
- Searchable Javadocs (finally!!!!)
- New Features
countBydistinctBy- Factories for Primitive Streams
- ImmutableCollection
CountBy
// Eclipse Collections 7.x - 8.x
MutableBag<String> countsOld =
this.company.getCustomers()
.asLazy().collect(Customer::getCity).toBag();
// Eclipse Collections 9.x
MutableBag<String> countsNew =
this.company.getCustomers()
.countBy(Customer::getCity);
DistinctBy
// Eclipse Collections 7.x - 8.x
MutableList<Customer> distinctOld =
this.company.getCustomers()
.distinct(HashingStrategies
.fromFunction(Customer::getCity));
// Eclipse Collections 9.x
MutableList<Customer> distinctNew =
this.company.getCustomers()
.distinctBy(Customer::getCity);
Factories for Primitive Streams
// Eclipse Collections 7.x - 8.x
MutableIntList listOld =
IntStream.rangeClosed(1, 100)
.collect(IntLists.mutable::empty,
MutableIntList::add,
MutableIntList::withAll);
// Eclipse Collections 9.x
MutableIntList listNew =
IntLists.mutable.withAll(
IntStream.rangeClosed(1, 100));
Stream on Immutable Collections
// Eclipse Collections 7.x - 8.x
boolean result =
Lists.immutable.with(1, 2, 3)
.castToList()
.stream()
.anyMatch(i -> i % 2 == 0);
// Eclipse Collections 9.x
boolean result =
Lists.immutable.with(1, 2, 3)
.stream()
.anyMatch(i -> i % 2 == 0);
ImmutableList API Design
Structural and Contractual Immutability
How did we get here?
- Once upon a time...
An old dude who knows Smalltalk
- Clipper / CA-Visual Objects (‘90 –‘95)
- Arrays
- Code Blocks
{ |each| expression }
- IBM VisualAge Smalltalk (‘94 – ‘00)
- Collections
- Code Blocks
[ :each | expression ]
Smalltalk-80 Collection Hierarchy
"It's turtles all the way down"
Smalltalk Collection Factories
|set list bag array string map interval|
set := Set with: 1 with: 2 with: 3 with: 4.
list := OrderedCollection with: 1 with: 2 with: 3 with: 4.
bag := Bag with: 1 with: 2 with: 3 with: 4.
array := #( 1 2 3 4 ).
string := ‘The Quick Brown Fox jumps over a lazy dog.’.
map := Dictionary newFromPairs: #( 1 ‘1’ 2 ‘2’ 3 ‘3’ 4 ‘4’).
interval := 1 to: 4.
Smalltalk Protocols (Eager)
set select: [ :each | each odd ]. // Set(1 3)
list reject: [ :each | each even]. // OrderedCollection(1 3)
array collect: [ :each | each asString]. // #(‘1’ ‘2’ ‘3’ ‘4’)
string detect: [ :c | c isLowercase]. // $h
map inject: '' into: [ :r :s | r, s]. // ‘1234’
string anySatisfy: [ :c | c = $e]. // true
set allSatisfy: [ :each | each < 5 ]. // true
map noneSatisfy: [ :s | s isString ]. // false
interval asSet. // Set(1 2 3 4)
map asBag. // Bag(‘1’ ‘2’ ‘3’ ‘4’)
list asArray. // #(1 2 3 4)
Smalltalk + Java = Eclipse Collections
| Smalltalk Influenced | Java Influenced |
|---|---|
| Symmetry | Basic Collection Types |
| Blocks (Lambdas) | Primitive Collections |
| Internal Iterators | Lazy Iterables |
| Protocol Naming | Parallelism |
| Eager APIs | Immutability |
| Bag, Interval | Multimap, BiMap |
| Factory Methods |
Designing API Names
- Naming is hard
- You can't make everyone happy
- Choose a naming influence style
- I chose Smalltalk as my influence
- Find patterns of implementation and name them
- Does your name communicate well?
- Get opinions of others
- Stick to your patterns and names - be consistent!
Primary API Comparison
| Eclipse Collections | Java 8 Streams |
|---|---|
select | filter |
reject | filter (!) |
collect | map |
flatCollect | flatMap |
detect | filter().findFirst().orElse(null) |
detectIfNone | filter().findFirst().orElseGet() |
detectOptional | filter().findFirst() |
count | filter().count() |
injectInto | reduce |
any | |
all | |
noneSatisfy | any/all/noneMatch |
Advanced API Comparison
| Eclipse Collections | Java 8 Streams |
|---|---|
groupBy | collect(Collectors.groupingBy) |
countBy | collect(Collectors.groupingBy(Collectors.counting)) |
aggregateBy | collect(Collectors.groupingBy(Collectors.reducing)) |
minByOptional | min(Comparator.comparing) |
maxByOptional | max(Comparator.comparing) |
distinct | distinct.collect(Collectors.toList) |
partition | collect(Collectors.partitioningBy) |
chunk | |
zip | |
makeString | collect(Collectors.joining) |
Eclipse Collections Factories
MutableSet<Integer> set = Sets.mutable.with(1, 2, 3, 4);
MutableList<Integer> list = Lists.mutable.with(1, 2, 3, 4);
MutableBag<Integer> bag = Bags.mutable.with(1, 2, 3, 4);
MutableList<Integer> array = ArrayAdapter.adapt(1, 2, 3, 4);
CharAdapter string =
CharAdapter.adapt("The Quick Brown Fox jumps over a lazy dog.");
MutableMap<Integer, String> map :=
Maps.mutable.with(1, "1", 2, "2", 3, "3", 4, "4").
Interval interval = Interval.oneTo(4);
Eclipse Collections (Eager)
set.select(each -> each % 2 == 1); // Set(1 3)
list.reject(each -> each % 2 == 0); // List(1 3)
array.collect(Object::toString); // List("1" "2" "3" "4")
bag.detect(each -> each < 2); // Integer.valueOf(1)
map.injectInto("", (r, s) -> r + s); // "1234"
string.anySatisfy(c -> c == 'e'); // true
set.allSatisfy(each -> each < 5); // true
map.noneSatisfy(String.class::isInstance); // false
interval.toSet(); // Set(1 2 3 4)
map.toBag(); // Bag("1" "2" "3" "4")
set.toList(); // List(1 2 3 4)
Eclipse Collections (Lazy)
set.asLazy().select(each -> each % 2 == 1); // Set(1 3)
list.asLazy().reject(each -> each % 2 == 0); // List(1 3)
array.asLazy().collect(Object::toString); // List("1" "2" "3" "4")
bag.asLazy().detect(each -> each < 2); // Integer.valueOf(1)
map.asLazy().injectInto("", (r, s) -> r + s); // "1234"
string.asLazy().anySatisfy(c -> c == 'e'); // true
set.asLazy().allSatisfy(each -> each < 5); // true
map.asLazy().noneSatisfy(String.class::isInstance); // false
interval.asLazy().toSet(); // Set(1 2 3 4)
map.asLazy().toBag(); // Bag("1" "2" "3" "4")
set.asLazy().toList(); // List(1 2 3 4)
The Past
- Memory efficient Small Collections (2004)
- Java 5 - Generics (2005)
- Open Source GS Collections (2012)
- Java 8 - Lambdas (2014)
- Eclipse Collections (2015)
The Future
- Modularization
- Improve Symmetry
- Object / Primitive
- New Data Structures
- More Primitive, Ordered, Trees, Persistent Structures, Off-heap
- Continued Java integration and inter-op
- Integrate with Eclipse Build Train
Appendix
Static Utility - Iterate (Eager)
Iterate.select(Iterable, Predicate); // Collection
Iterate.reject(Iterable, Predicate); // Collection
Iterate.collect(Iterable, Function); // Collection
Iterate.detect(Iterable, Predicate); // Object
Iterate.injectInto(Iterable, Object, Function2); // Object
Iterate.anySatisfy(Iterable, Predicate); // boolean
Iterate.allSatisfy(Iterable, Predicate); // boolean
Iterate.noneSatisfy(Iterable, Predicate); // boolean
Iterate.toArray(Iterable); // Object[]
Iterate.toMap(Iterable, Function, Function); // Map
Static Utility - ListIterate (Eager)
ListIterate.select(List, Predicate); // MutableList
ListIterate.reject(List, Predicate); // MutableList
ListIterate.collect(List, Function); // MutableList
ListIterate.detect(List, Predicate); // Object
ListIterate.injectInto(List, Object, Function2); // Object
ListIterate.anySatisfy(List, Predicate); // boolean
ListIterate.allSatisfy(List, Predicate); // boolean
ListIterate.noneSatisfy(List, Predicate); // boolean
ListIterate.toArray(List); // Object[]
ListIterate.toMap(List, Function, Function); // Map
Eclipse Collections Primitive (Eager)
set.select(each -> each % 2 == 1); // IntSet(1 3)
list.reject(each -> each % 2 == 0); // IntList(1 3)
bag.collect(Object::toString); // Bag("1" "2" "3" "4")
string.detect(Character::isLowerCase); // 'h'
map.injectInto("", (r, s) -> r + s); // "1234"
string.anySatisfy(c -> c == 'e'); // true
set.allSatisfy(each -> each < 5); // true
map.noneSatisfy(String.class::isInstance); // false
interval.toSet(); // IntSet(1 2 3 4)
map.toBag(); // IntBag(1 2 3 4)
set.toList(); // IntList(1 2 3 4)
Eclipse Collections Primitive (Lazy)
set.asLazy().select(each -> each % 2 == 1); // LazyIntIterable
list.asLazy().reject(each -> each % 2 == 0); // LazyIntIterable
bag.asLazy().collect(Object::toString); // LazyIterable
string.asLazy().detect(Character::isLowerCase); // 'h'
map.asLazy().injectInto("", (r, s) -> r + s); // "1234"
string.asLazy().anySatisfy(c -> c == 'e'); // true
set.asLazy().allSatisfy(each -> each < 5); // true
map.asLazy().noneSatisfy(String.class::isInstance); // false
interval.asLazy().toSet(); // IntSet(1 2 3 4)
map.asLazy().toBag(); // IntBag(1 2 3 4)
set.asLazy().toList(); // IntList(1 2 3 4)
Eclipse Collections (Streams)
set.stream().filter(each -> each % 2 == 1); // Stream
list.stream().filter(each -> each % 2 != 0); // Stream
array.stream().map(Object::toString); // Stream
bag.stream().filter(each -> each < 2).findFirst(); // Optional(1)
map.stream().reduce("", (r, s) -> r + s); // "1234"
string.chars().anyMatch(c -> c == 'e'); // true
set.stream().allMatch(each -> each < 5); // true
map.stream().noneMatch(String.class::isInstance); // false
interval.stream().collect(Collectors.toSet()); // Set(1 2 3 4)
map.stream().collect(Collectors2.toBag()); // Bag(1 2 3 4)
set.stream().collect(Collectors2.toList()); // List(1 2 3 4)