Rich, Lazy, Mutable, and Immutable Interfaces in Eclipse Collections
Learn about Java collection interfaces with intention revealing names.

A quick guide to reading this blog
I wrote this blog to help explain the design of the Eclipse Collections interface hierarchies to folks who are new to the library and might find the large number of interfaces disorienting to learn by browsing through code and/or Javadoc. This blog should also be a good reference for folks that understand the design of Eclipse Collections, but occasionally want a quick link to find, validate, or explain some things.
The rest of this blog will explain the relationship between Rich
, Lazy
, Mutable
, and Immutable
types in Eclipse Collections. The end of this blog has a reference section with a list of all the unique methods that show why Eclipse Collection is a “rich” collections library for Java. Click the link to skip there if that is what you are looking for.
Enjoy!
Everything (almost) extends Iterable
All object containers in Eclipse Collections, with the exception of Multimap
types, extend java.lang.Iterable
. This design decision makes it possible to use any Eclipse Collections interface with a Java 5 style for
each loop. This provides the most basic and flexible form of external iteration you expect from a Java Iterable
.
All container types in Eclipse Collections, including Multimap
types, favor providing internal iterators like forEach
that take a Functional Interface as a parameter that can be expressed as a lambda or method reference. It is not necessary to extend java.lang.Iterable
to support internal iterators. As of Java 8, even java.lang.Iterable
added a default
forEach
method to make internal iterators possible on JDK types.
Eclipse Collections has an interface named InteralIterable
that inherits directly from java.lang.Iterable
. The purpose of InternalIterable
is to provide internal iterators like forEach
to all Eclipse Collections types. There are also two other methods defined on InternalIterable
named forEachWith
and forEachWithIndex
.
The forEachWithIndex
method is marked as deprecated
because with unordered collections, an index is not meaningful and winds up being just an iteration counter instead. If you are looking for something to have an iteration counter over an unordered type, then forEachWithIndex
(while poorly named) might be useful to you.
The following code shows how a for
loop, forEach
and forEachWith
method can be used with any InternalIterable
in Eclipse Collections.
@Test
public void forLoopVsForEachVsForEachWith()
{
InternalIterable<String> iterable = Lists.immutable.with("a", "b", "c");
// Java 5 for each loop
for (String each : iterable)
{
this.output(each, String::toUpperCase);
}
// forEach with lambda
iterable.forEach(each -> this.output(each, String::toUpperCase));
// forEachWith with method reference
iterable.forEachWith(this::output, String::toUpperCase);
}
public void output(String each, Function<String, String> function)
{
System.out.print(function.valueOf(each));
}
// Output:
// ABCABCABC
The methods forEach
, forEachWith
, forEachWithIndex
are occasionally useful. There are many more useful methods on the RichIterable
interface and its child interfaces LazyIterable
, MutableCollection
, and ImmutableCollection
.
The Child and Grandchildren of InternalIterable
The four “rich” types in Eclipse Collections that extend InternalIterable
are RichIterable
, LazyIterable
, MutableCollection
and ImmutableCollection
. The hierarchy of these types is shown in the following class diagram.

The interfaces in the preceding diagram communicate their capabilities through the prefix in their names — Rich
, Lazy
, Mutable
, and Immutable
. Developers can leverage these names to communicate their intent to developers who use their APIs.
- Rich — Read-only, serial. Lazy or eager behavior (up to implementation).
- Lazy — Read-only, serial, lazy behavior for non-terminal methods.
- Mutable — Serial, eager, with mutating methods for growth
- Immutable — Serial, eager, with non-mutating methods for growth
What does the “Rich” in RichIterable mean?
The design philosophy behind RichIterable
favored defining useful internal iteration patterns that were seen in production code directly on the interface. There are many useful iteration patterns we saw in production code over the years, so we named them as best we could, and added them as features to the RichIterable
interface. We thought the plethora of methods made this a feature-rich interface. This is how we arrived at the name RichIterable
for the parent interface for most of the types in Eclipse Collections. RichIterable
is an Iterable
that is feature-rich.
RichIterable
provides many methods that iterate over a container or view and perform some useful algorithms. RichIterable
is a serial and read-only interface, so it has no methods that mutate the underlying container. RichIterable
can be useful as both a parameter and return type. Methods that employ RichIterable
can either accept or return LazyIterable
, MutableCollection
, or ImmutableCollection
types, while only exposing the read-only API. There is no guarantee that methods on RichIterable
are lazy or eager, or that the types are mutable or immutable. That decision is left up to the implementation types. If you want something to explicitly be mutable or immutable, then it is better to use the more explicit named types with these prefixes.
How many methods makes a feature-rich interface?
I don’t know if there is a minimum method count for an interface to qualify as feature-rich. I counted 176
methods defined in RichIterable
that were not defined in InternalIterable
or Iterable
. I believe this method count qualifies.
One Hundred Seventy Six methods = Most certainly feature-rich
Click this link or scroll to the bottom of the blog to see the list of unique methods with return types defined on RichIterable<T>
. I moved this list to the bottom of the blog because it has 130 unique methods. That is a lot to scroll past to continue reading. I do encourage you to check out the list and see what kind of useful methods are included in RichIterable
.
The following sections will show the methods in RichIterable
that have covariant overrides in LazyIterable
, MutableCollection
, and ImmutableCollection
.
LazyIterable
LazyIterable
is similar to RichIterable
in that it is serial, read-only and inherits all of the same methods. All of the methods that return an Iterable
type in RichIterable
have covariant overrides in LazyIterable
that return LazyIterable
. All the non-terminal methods on LazyIterable
are lazy. LazyIterable
is similar to java.util.stream.Stream
in that it supports lazy iteration patterns, but it is different in that a LazyIterable
instance can be reused safely for multiple operations without throwing an “exhausted” RuntimeException
.
Covariant Overrides
The following methods are overridden from RichIterable
on LazyIterable
and use covariant returns. What this means is that the return types are more specific in LazyIterable
than the return types in RichIterable
. The return types must be a subtype of the return type defined in RichIterable
.
chunk
➡️LazyIterable<RichIterable<T>>
collect
➡️LazyIterable<V>
collectBoolean
➡️LazyBooleanIterable
collectByte
➡️LazyByteIterable
collectChar
➡️LazyCharIterable
collectDouble
➡️LazyDoubleIterable
collectFloat
➡️LazyFloatIterable
collectIf
➡️LazyIterable<V>
collectInt
➡️LazyIntIterable
collectLong
➡️LazyLongIterable
collectShort
➡️LazyShortIterable
collectWith
➡️LazyIterable<V>
flatCollect
➡️LazyIterable<V>
flatCollectWith
➡️LazyIterable<V>
reject
➡️LazyIterable<T>
rejectWith
➡️LazyIterable<T>
select
➡️LazyIterable<T>
selectInstancesOf
➡️LazyIterable<S>
selectWith
➡️LazyIterable<T>
tap
➡️LazyIterable<T>
MutableCollection
MutableCollection
inherits all of the methods of RichIterable
, and it is mutable as the prefix implies. Methods that mutate the underlying container such as add
and remove
are provided. All of the methods that return an Iterable
type have covariant overrides in MutableCollection
that return MutableCollection
. All methods on MutableCollection
, with the exception of asLazy
, are serial and eager. Eager iteration patterns are very easy to understand, as they most closely resemble the code a developer would write by hand implementing an iteration pattern using a for
loop.
Covariant Overrides
The following methods are overridden from RichIterable
on MutableCollection
and use covariant returns. What this means is that the return types are more specific in MutableCollection
than the return types in RichIterable
. The return types must be a subtype of the return type defined in RichIterable
.
aggregateBy
➡️MutableMap<K, V>
aggregateInPlaceBy
➡️MutableMap<K, V>
collect
➡️MutableCollection<V>
collectBoolean
➡️MutableBooleanCollection
collectByte
➡️MutableByteCollection
collectChar
➡️MutableCharCollection
collectDouble
➡️MutableDoubleCollection
collectFloat
➡️MutableFloatCollection
collectIf
➡️MutableCollection<V>
collectInt
➡️MutableIntCollection
collectLong
➡️MutableLongCollection
collectShort
➡️MutableShortCollection
collectWith
➡️MutableCollection<V>
countBy
➡️MutableBag<V>
countByEach
➡️MutableBag<V>
countByWith
➡️MutableBag<V>
flatCollect
➡️MutableCollection<V>
flatCollectWith
➡️MutableCollection<V>
groupBy
➡️MutableMultimap<V, T>
groupByEach
➡️MutableMultimap<V, T>
groupByUniqueKey
➡️MutableMap<V, T>
partition
➡️PartitionMutableCollection<T>
partitionWith
➡️PartitionMutableCollection<T>
reject
➡️MutableCollection<T>
rejectWith
➡️MutableCollection<T>
select
➡️MutableCollection<T>
selectInstancesOf
➡️MutableCollection<S>
selectWith
➡️MutableCollection<T>
sumByDouble
➡️MutableObjectDoubleMap<V>
sumByFloat
➡️MutableObjectDoubleMap<V>
sumByInt
➡️MutableObjectLongMap<V>
sumByLong
➡️MutableObjectLongMap<V>
tap
➡️MutableCollection<T>
ImmutableCollection
ImmutableCollection
is similar to RichIterable
in that it is read-only and inherits all of the same methods. All of the methods that return an Iterable
type have covariant overrides in ImmutableCollection
that return ImmutableCollection
. All methods on ImmutableCollection
, with the exception of asLazy
, are serial and eager. Eager iteration patterns are very easy to understand, as they most closely resemble the code a developer would write by hand implementing an iteration pattern using a for
loop.
Covariant Overrides
The following methods are overridden from RichIterable
on ImmutableCollection
and use covariant returns. What this means is that the return types are more specific in ImmutableCollection
than the return types in RichIterable
. The return types must be a subtype of the return type defined in RichIterable
.
aggregateBy
➡️ImmutableMap<K, V>
aggregateInPlaceBy
➡️ImmutableMap<K, V>
collect
➡️ImmutableCollection<V>
collectBoolean
➡️ImmutableBooleanCollection
collectByte
➡️ImmutableByteCollection
collectChar
➡️ImmutableCharCollection
collectDouble
➡️ImmutableDoubleCollection
collectFloat
➡️ImmutableFloatCollection
collectIf
➡️ImmutableCollection<V>
collectInt
➡️ImmutableIntCollection
collectLong
➡️ImmutableLongCollection
collectShort
➡️ImmutableShortCollection
collectWith
➡️ImmutableCollection<V>
countBy
➡️ImmutableBag<V>
countByEach
➡️ImmutableBag<V>
countByWith
➡️ImmutableBag<V>
flatCollect
➡️ImmutableCollection<V>
flatCollectWith
➡️ImmutableCollection<V>
groupBy
➡️ImmutableMultimap<V, T>
groupByEach
➡️ImmutableMultimap<V, T>
groupByUniqueKey
➡️ImmutableMap<V, T>
partition
➡️PartitionImmutableCollection<T>
partitionWith
➡️PartitionImmutableCollection<T>
reject
➡️ImmutableCollection<T>
rejectWith
➡️ImmutableCollection<T>
select
➡️ImmutableCollection<T>
selectInstancesOf
➡️ImmutableCollection<S>
selectWith
➡️ImmutableCollection<T>
sumByDouble
➡️ImmutableObjectDoubleMap<V>
sumByFloat
➡️ImmutableObjectDoubleMap<V>
sumByInt
➡️ImmutableObjectLongMap<V>
sumByLong
➡️ImmutableObjectLongMap<V>
tap
➡️ImmutableCollection<T>
Diamonds grow on trees
Some of the Diamond hierarchies that formed in Eclipse Collections were the result of the Mutable
part of the Eclipse Collections hierarchy intersecting with the JDK interfaces (Collection
➡️ MutableCollection
, List
➡️ MutableList
, Set
➡️ MutableSet
). I described some of these diamonds in the following blog.
Other diamonds become visible as you move down the hierarchy to more specific types. There is a pattern in the type hierarchy that formed at the top which results in many instances of tree hierarchies with an Iterable
, Mutable
, and Immutable
type.
For each container type in the following list, there is a tree with an Iterable
, Mutable
, and Immutable
type.
Bag
➡️MutableBag
/ImmutableBag
ListIterable
➡️MutableList
/ImmutableList
SetIterable
➡️MutableSet
/ImmutableSet
StackIterable
➡️MutableStack
/ImmutableStack
MapIterable
➡️MutableMap
/ImmutableMap
Multimap
➡️MutableMultimap
/ImmutableMultimap
- etc.
The following diagram shows the diamonds that form as the RichIterable
and ListIterable
trees connect. A similar diamond formation happens for each container type listed above.

A similar symmetry exists in the Eclipse Collections primitive interface hierarchy as well. There are a lot more types in the primitive hierarchy because the hierarchy is a combination of container type and primitive type. At the top of the hierarchy is a type called PrimitiveIterable
, which has a small set of common methods between primitive types. The following list shows a sample of the trees that exist for the primitive types in Eclipse Collections using the int
type as an example.
IntIterable
➡️MutableIntCollection
/ImmutableIntCollection
IntList
➡️MutableIntList
/ImmutableIntList
IntSet
➡️MutableIntSet
/ImmutableIntSet
- etc.
The following diagram shows the diamonds that form as the IntIterable
and IntList
trees connect.

I hope these diagrams share a small bit of the symmetry that exists in Eclipse Collections. I have included the select
method in both the object and primitive hiearchy examples to illustrate the covariant return types at each interface level.
Symmetry in most things
Understanding the symmetry that exists between Iterable
, Mutable
, and Immutable
types across different containers should make it easier to understand the various types, trees and diamonds that exist in the Eclipse Collections library. This library has evolved over almost two decades, and occasionally we still find some asymmetry that exists. We will fix issues like this when there is a measurable benefit. For the most part, if you would expect something to exist based on your understanding of how other things are defined, the symmetry will probably exist and meet your expectations.
Thank you for reading this blog, and I hope you learned something useful about the library along the way!
All 130 Unique Methods on RichIterable
The list below has all 130 unique methods in RichIterable
with links to Javadoc. This list is a bit more concise than the Javadoc for RichIterable
and should make it easier to find things quickly. The method names appear first with the return types second separated by the ➡️ emoji.
aggregateBy
➡️MapIterable<K, V>
aggregateInPlaceBy
➡️MapIterable<K, V>
allSatisfy
➡️boolean
allSatisfyWith
➡️boolean
anySatisfy
➡️boolean
anySatisfyWith
➡️boolean
appendString
➡️void
asLazy
➡️LazyIterable<T>
chunk
➡️RichIterable<RichIterable<T>>
collect
➡️RichIterable<V>
collectBoolean
➡️BooleanIterable
collectByte
➡️ByteIterable
collectChar
➡️CharIterable
collectDouble
➡️DoubleIterable
collectFloat
➡️FloatIterable
collectIf
➡️RichIterable<V>
collectInt
➡️IntIterable
collectLong
➡️LongIterable
collectShort
➡️ShortIterable
collectWith
➡️RichIterable<V>
contains
➡️boolean
containsAll
➡️boolean
containsAllArguments
➡️boolean
containsAllIterable
➡️boolean
containsAny
➡️boolean
containsAnyIterable
➡️boolean
containsBy
➡️boolean
containsNone
➡️boolean
containsNoneIterable
➡️boolean
count
➡️int
countBy
➡️Bag<V>
countByEach
➡️Bag<V>
countByWith
➡️Bag<V>
countWith
➡️int
detect
➡️T
detectIfNone
➡️T
detectOptional
➡️Optional<T>
detectWith
➡️T
detectWithIfNone
➡️T
detectWithOptional
➡️Optional<T>
each
➡️void
flatCollect
➡️RichIterable<V>
flatCollectBoolean
➡️R extends BooleanIterable
flatCollectByte
➡️R extends ByteIterable
flatCollectChar
➡️R extends CharIterable
flatCollectDouble
➡️R extends DoubleIterable
flatCollectFloat
➡️R extends FloatIterable
flatCollectInt
➡️R extends IntIterable
flatCollectLong
➡️R extends LongIterable
flatCollectShort
➡️R extends ShortIterable
flatCollectWith
➡️RichIterable<V>
forEach
➡️void
getAny
➡️T
getFirst
➡️T
getLast
➡️T
getOnly
➡️T
groupBy
➡️Multimap<V, T>
groupByAndCollect
➡️R extends MutableMultimap<K, V>
groupByEach
➡️Multimap<V, T>
groupByUniqueKey
➡️MapIterable<V, T>
injectInto
➡️IV
injectIntoDouble
➡️double
injectIntoFloat
➡️float
injectIntoInt
➡️int
injectIntoLong
➡️long
into
➡️R
isEmpty
➡️boolean
makeString
➡️String
max
➡️T
maxBy
➡️T
maxByOptional
➡️Optional<T>
maxOptional
➡️Optional<T>
min
➡️T
minBy
➡️T
minByOptional
➡️Optional<T>
minOptional
➡️Optional<T>
noneSatisfy
➡️boolean
noneSatisfyWith
➡️boolean
notEmpty
➡️boolean
partition
➡️PartitionIterable<T>
partitionWith
➡️PartitionIterable<T>
reduce
➡️Optional<T>
reduceInPlace
➡️R
reject
➡️RichIterable<T>
rejectWith
➡️RichIterable<T>
select
➡️RichIterable<T>
selectInstancesOf
➡️RichIterable<S>
selectWith
➡️RichIterable<T>
size
➡️int
sumByDouble
➡️ObjectDoubleMap<V>
sumByFloat
➡️ObjectDoubleMap<V>
sumByInt
➡️ObjectLongMap<V>
sumByLong
➡️ObjectLongMap<V>
summarizeDouble
➡️DoubleSummaryStatistics
summarizeFloat
➡️DoubleSummaryStatistics
summarizeInt
➡️IntSummaryStatistics
summarizeLong
➡️LongSummaryStatistics
sumOfDouble
➡️double
sumOfFloat
➡️double
sumOfInt
➡️long
sumOfLong
➡️long
tap
➡️RichIterable<T>
toArray
➡️Object[]
toBag
➡️MutableBag<T>
toBiMap
➡️MutableBiMap<K, V>
toImmutableBag
➡️ImmutableBag<T>
toImmutableBiMap
➡️ImmutableBiMap<K, V>
toImmutableList
➡️ImmutableList<T>
toImmutableMap
➡️ImmutableMap<K, V>
toImmutableSet
➡️ImmutableSet<T>
toImmutableSortedBag
➡️ImmutableSortedBag<T>
toImmutableSortedBagBy
➡️ImmutableSortedBag<T>
toImmutableSortedList
➡️ImmutableList<T>
toImmutableSortedListBy
➡️ImmutableList<T>
toImmutableSortedSet
➡️ImmutableSortedSet<T>
toImmutableSortedSetBy
➡️ImmutableSortedSet<T>
toList
➡️MutableList<T>
toMap️
➡️MutableMap<K, V>
toSet
➡️MutableSet<T>
toSortedBag
➡️MutableSortedBag<T>
toSortedBagBy
➡️MutableSortedBag<T>
toSortedList
➡️MutableList<T>
toSortedListBy
➡️MutableList<T>
toSortedMap
➡️MutableSortedMap<K, V>
toSortedMapBy
➡️MutableSortedMap<K, V>
toSortedSet
➡️MutableSortedSet<T>
toSortedSetBy
➡️MutableSortedSet<T>
toString
➡️String
zip
➡️RichIterable<Pair<T, S>>
zipWithIndex
➡️RichIterable<Pair<T, Integer>>
I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.