FastUtil High‑Performance Collection Best Practices: Speed Up Your Java Programs
FastUtil, an open‑source library maintained by Sebastiano Vigna, offers type‑specialized Java collections that are typically 2–5× faster and use 40–70% less memory than JDK equivalents, and this article provides detailed benchmarks, a cheat‑sheet of core types, production‑ready code snippets, Maven/Gradle setup, and a checklist of common pitfalls to help developers adopt FastUtil safely and efficiently.
Why Choose FastUtil?
FastUtil is an open‑source library maintained by Italian computer scientist Sebastiano Vigna. It provides type‑specialized collection implementations for Java primitive types, delivering performance that is usually 2–5× faster than the standard JDK collections and reducing memory consumption by 40–70%. These gains make FastUtil a de‑facto choice for high‑performance back‑ends, game servers, big‑data processing, and quantitative trading.
Benchmark Highlights
For a map holding ten million entries:
JDK HashMap consumes ~1.1 GB, while Int2LongOpenHashMap uses ~320 MB.
Put/get operations are 2.8–4.5× faster with FastUtil.
Garbage‑collection pressure drops from high (due to many boxed Integer / Long objects) to negligible because FastUtil stores primitives without boxing.
Similar improvements are observed for object‑based maps: Object2ObjectOpenHashMap reduces memory to ~720 MB and speeds up operations 1.5–3× compared with HashMap.
Core Collection Cheat Sheet
FastUtil supplies a family of collections for each primitive type. The most commonly needed eight types cover about 95 % of everyday scenarios:
Object : ObjectArrayList, ObjectOpenHashSet, Object2ObjectOpenHashMap int : IntArrayList, IntOpenHashSet, Int2IntOpenHashMap, Int2ObjectOpenHashMap long : LongArrayList, LongOpenHashSet, Long2LongOpenHashMap, Long2ObjectOpenHashMap double : DoubleArrayList, DoubleOpenHashSet, Double2DoubleOpenHashMap float : FloatArrayList, FloatOpenHashSet byte : ByteArrayList, ByteOpenHashSet, Byte2IntOpenHashMap char : CharArrayList, CharOpenHashSet short : ShortArrayList For all primitive‑key maps, the OpenHash series (e.g., Int2LongOpenHashMap) is the default implementation and outperforms older RBTree or CHAMP variants in both speed and memory usage.
Best‑Practice Code Samples
Basic Replacement
// Poor: heavy boxing + high memory
Map<Integer, Long> map = new HashMap<>();
// Good: zero boxing, extreme performance
Int2LongOpenHashMap map = new Int2LongOpenHashMap();
// Common constructors with capacity and load factor
Int2LongOpenHashMap map = new Int2LongOpenHashMap(1_000_000);
Int2LongOpenHashMap map = new Int2LongOpenHashMap(1_000_000, 0.8f); // custom load factorInitialization for Large Maps
// Estimate capacity and use a high load factor (FastUtil default 0.8–0.9)
int expectedSize = 5_000_000;
Int2ObjectOpenHashMap<User> userMap = new Int2ObjectOpenHashMap<>(expectedSize, 0.9f);
// If occasional rehash is acceptable, load factor can be raised to 0.95f
Int2IntOpenHashMap counter = new Int2IntOpenHashMap(100_000, 0.95f);High‑Frequency Operations
// 1. get with default value (avoid containsKey + get)
long value = map.getOrDefault(userId, 0L); // ~2× faster than containsKey + get
// 2. Counter pattern (addTo is atomic and 3–5× faster than compute)
map.addTo(userId, 1L);
// 3. Bulk insertion (FastUtil‑specific API, ~30% faster than putAll)
int[] keys = ...;
long[] values = ...;
map.putAll(IntArrays.forceCopy(keys), LongArrays.forceCopy(values), keys.length);List Usage
// Dynamic primitive list (2–5× faster than ArrayList, less memory)
IntArrayList list = new IntArrayList();
list.add(1);
list.add(2);
int[] array = list.elements(); // zero‑copy, do not modify list afterwards
int[] safeArray = list.toIntArray(); // defensive copy
// Wrap an existing array without copying
int[] raw = {1,2,3,4};
IntArrayList list = IntArrayList.wrap(raw);Set Usage
IntOpenHashSet set = new IntOpenHashSet(1_000_000, 0.9f);
set.add(123);
if (set.add(123)) { /* first insertion */ }
int[] array = set.toArray(new int[set.size()]);Integration with Java Streams
// FastUtil raw iterator stream (5–10× faster than boxed stream)
long sum = map.int2LongEntrySet()
.fastForEach(entry -> total += entry.getLongValue());
// Parallel processing for large string collections
map.int2LongEntrySet().parallelStream()
.forEach(entry -> updateSomeGlobalCounter(entry));Serialization Tips
// FastUtil implements Serializable; declare serialVersionUID explicitly
private static final long serialVersionUID = 1L;
// For huge maps, use FastUtil binary format (5–10× faster than JDK serialization)
ByteBufferOutput out = ...;
Int2LongBinaryOpenHashMap.write(out, map);String/Object Replacement
// Poor: generic HashMap with boxing overhead
Map<String, Object> map = new HashMap<>();
// Good: FastUtil object‑optimized map
Object2ObjectOpenHashMap objMap = new Object2ObjectOpenHashMap();
Object2ObjectOpenHashMap strMap = new Object2ObjectOpenHashMap();
int expectedSize = 500_000;
objMap = new Object2ObjectOpenHashMap(expectedSize, 0.9f);
strMap = new Object2ObjectOpenHashMap(expectedSize, 0.9f);
// Enable reference equality for further 20–30% speedup (ensure no null keys)
objMap.referenceEquality();Dependency Setup (2025)
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.5.15</version>
</dependency>
// Gradle Kotlin DSL
implementation("it.unimi.dsi:fastutil:8.5.15")Production Pitfalls & Correct Practices
Don’t instantiate new HashMap; replace with new Int2XxxOpenHashMap() for primitive keys.
Avoid boxed return values; use primitive getters like map.getOrDefault(intKey, 0L).
Replace ArrayList with the appropriate IntArrayList, LongArrayList, etc.
Prefer primitive‑for‑each loops ( for (int i : list)) or FastUtil iterators over boxed for (Integer i : list).
For large map serialization, use FastUtil’s binary API instead of default Java serialization.
When concurrent modifications cause exceptions, apply segment locks or external synchronization with FastUtil maps.
Enable referenceEquality() for String keys to gain 20–30% speed when nulls are not used.
Conclusion – One Replacement Rule
If the key or value is a primitive type and the expected size exceeds size > 100 000, switch to the corresponding FastUtil collection.
For String/Object keys, when size > 50 000 or operations are frequent, replace HashMap with Object2ObjectOpenHashMap to achieve at least 20 % performance improvement.
In Java, boxing is a performance killer ; FastUtil is the antidote.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
