JVM Advent Calendar: API Design of Eclipse Collections

DZone 's Guide to

JVM Advent Calendar: API Design of Eclipse Collections

Learn more about Eclipse Collections and the API design.

· Java Zone ·
Free Resource

Eclipse Collections is an open-source Java Collections Framework that enables writing functional, fluent code in Java.


Eclipse Collections started off as a collections framework named Caramel at Goldman Sachs in 2004. Since then, the framework has evolved, and in 2012, it was open sourced to GitHub as a project called GS Collections. Over the years, around 40 or so developers from the same company have contributed to the collections framework. To maximize the best nature of the open-source project, GS Collections was migrated to the Eclipse Foundation, re-branded as Eclipse Collections in 2015. Now, the framework is fully open to the community, accepting contributions!

Design Goals

Eclipse Collections was designed to provide rich, functional, fluent, and fun APIs along with memory efficient data structures, all the while providing interoperability with Java Collections. It provides missing types like Bag, Multimap, Stack, BiMap, Interval.

Evolution of the Framework

Over the past 14+ years, the framework has matured and the highest interface: RichIterable, which now has more than 100 methods on it. These methods were included on the interface after careful deliberation. Below are the steps we take while adding an API:

1. Use case: the majority of the methods added to the framework are motivated by user requirements. Users will raise either an Issue or directly a Pull Request on the project and then we start the discussion.

2. Static Utility vs API: Eclipse Collections has static utility classes like Iterate, ListIterate, etc. These static utility classes allow us to prototype our features before adding it as an API. If the static utility methods are heavily used, then in subsequent release, we try to implement the method as an API on the collection interface to provide a rich and fluent coding experience.

For example, Iterate#groupByAndCollect() is currently implemented on the static utility. Since the method is used frequently, it justifies adding it as an API on RichIterable to provide a rich, functional, and fluent coding experience. There is an open issue in case you would like to help us out.

3. Covariant Overrides: We override the API methods logically so that the API returns a type, which is true to it’s behavior.

For example, RichIterable has an API called select(), which is similar to filter() and returns all elements of the collections, which evaluate true for the Predicate. Below is how the API is defined on each interface:

// RichIterable
RichIterable<T> select(Predicate<? super T> predicate);

// ListIterable
ListIterable<T> select(Predicate<? super T> predicate);

// MutableList
MutableList<T> select(Predicate<? super T> predicate)

As you can see, select() on
RichIterable returns a RichIterable
ListIterable returns a ListIterable
MutableList returns a MutableList

4. Overloads with Target: Sometimes, it is possible that we need a different collection than the one returned. In order to make it efficient and fluent, we create an overloaded method, which accepts a target collection. The target collections are used to accumulate the results and return the target collection.

For example, as described above, the select() method on a MutableList returns a MutableList. However, what if you want a MutableSet? There is an overloaded select() method available, which takes in a target collection that can be a set.

MutableList<Integer> integers = Lists.mutable.with(
        1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
MutableList<Integer> evens = integers.select(each -> each % 2 == 0);
Assert.assertEquals(Lists.mutable.with(2, 2, 4, 4, 4, 4), evens);

MutableSet<Integer> uniqueEvens = integers.select(
        each -> each % 2 == 0,
Assert.assertEquals(Sets.mutable.with(2, 4), uniqueEvens);

5. Symmetry:
Eclipse Collections offers primitive collections. We try to maintain symmetry between the object collections and primitive collections to provide a complete user experience.

Implementing an API in Practice

Let us implement a simple API RichIterable#countBy(), which was added in Eclipse Collections release 9.0.0: The motivation for this API was the users mentioning having to collect() a collection in a Bag. In Eclipse Collections, collect() is similar to map(), and Bagis a data structure that maintains a mapping of an object to the count.

MutableList<String> strings = Lists.mutable.with(
        "1", "2", "2", "3", "3", "3", "4", "4", "4", "4");
Bag<Integer> integers = strings.collect(
Assert.assertEquals(1, integers.occurrencesOf(1));
Assert.assertEquals(2, integers.occurrencesOf(2));
Assert.assertEquals(3, integers.occurrencesOf(3));
Assert.assertEquals(4, integers.occurrencesOf(4));

The above solution to count the integers worked; however, it was not intuitive. An inexperienced developer might have a hard time to implement this solution. So, we decided to add countBy(), and now, the code looks more functional, fluent, and intuitive.

MutableList<String> strings = Lists.mutable.with(
        "1", "2", "2", "3", "3", "3", "4", "4", "4", "4");
Bag<Integer> integers = strings.countBy(Integer::valueOf);
Assert.assertEquals(1, integers.occurrencesOf(1));
Assert.assertEquals(2, integers.occurrencesOf(2));
Assert.assertEquals(3, integers.occurrencesOf(3));
Assert.assertEquals(4, integers.occurrencesOf(4));


In this blog, I explained the evolution strategy of a mature Java collections library. The aspects we look at are use case, utility vs API, covariant overrides, necessary overloads, and lastly, symmetry.

It is a personal goal to get 1000 stars on our GitHub project, so if you like the framework, show your support and put a star on the repository. 

collections ,eclipse ,eclipse collections ,framework ,history ,iterate ,java

Published at DZone with permission of Nikhil Nanivadekar . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}