I still run into Enumeration in places where everything else is modern Java: servlet request parameter names, older libraries, and a surprising amount of “enterprise glue” code that never got rewritten. If you’ve been living in Iterator, enhanced for, and Streams for years, Enumeration can feel like a relic—until your integration point demands it and you need a clean adapter without rewriting a whole pipeline.
That’s exactly what java.util.Collections.enumeration(Collection c) gives you: a tiny bridge from any Collection to an Enumeration. In practice, it’s the fastest way to satisfy a legacy method signature while keeping the rest of your code in normal 2026-era patterns. You’ll see how the adapter behaves (including fail-fast surprises), how to use it safely with mutable collections, how to bridge back to modern types, and when you should avoid it entirely.
Why Enumeration Still Matters in 2026
When I’m reviewing codebases, Enumeration usually appears for one of three reasons:
1) You’re integrating with older APIs that still speak Enumeration. This is common in Java EE / Jakarta-era surfaces (and libraries inspired by them). A classic example is ServletRequest#getParameterNames(), which returns an Enumeration.
2) The library’s public API never evolved. Even if the implementation has been modernized internally, some libraries keep Enumeration in method signatures to avoid breaking consumers.
3) You’re interacting with the “legacy collections” ecosystem. Vector and Hashtable are still in the JDK and still show up in long-lived systems. They often expose iteration via Enumeration.
The important point: you usually don’t want to write new logic using Enumeration. You want a clean adapter at the boundary so the rest of your code stays on Collection, Iterator, and Streams.
What Collections.enumeration(Collection c) Actually Returns
The signature is:
public static Enumeration enumeration(Collection c)
Conceptually, it wraps the collection’s Iterator so you can iterate using the two Enumeration methods:
boolean hasMoreElements()T nextElement()
Under the hood, you can think of it like this (simplified):
// Conceptual model, not JDK source
Iterator it = c.iterator();
return new Enumeration() {
public boolean hasMoreElements() { return it.hasNext(); }
public T nextElement() { return it.next(); }
};
That mental model explains most real-world behavior:
- Fail-fast behavior typically follows the collection’s iterator. If the collection’s iterator is fail-fast (like
ArrayList), mutating the collection structurally while enumerating usually triggersConcurrentModificationException. - No remove operation.
Enumerationdoesn’t haveremove(). You can’t mutate via the enumeration itself. - It’s an adapter, not a snapshot. It doesn’t copy elements; it iterates over the same underlying data via the iterator.
If you need a stable view while other code might mutate the collection, you should create a defensive copy first.
Runnable Example: Enumerating a List
Here’s a complete program you can compile and run. I use realistic names and keep the output predictable.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationStringsDemo {
public static void main(String[] args) {
List attendees = new ArrayList();
attendees.add("Asha");
attendees.add("Miguel");
attendees.add("Priya");
System.out.println("List: " + attendees);
Enumeration e = Collections.enumeration(attendees);
System.out.println("\nEnumeration over list:");
while (e.hasMoreElements()) {
String name = e.nextElement();
System.out.println("Value is: " + name);
}
}
}
What I like about this example is that it shows the adapter is trivial to apply: you keep a normal List, and only convert at the point you need Enumeration.
Runnable Example: Enumerating a List
Same idea, different type. This is useful when you’re bridging to older code that expects numeric IDs.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationIntegersDemo {
public static void main(String[] args) {
List buildNumbers = new ArrayList();
buildNumbers.add(120);
buildNumbers.add(121);
buildNumbers.add(122);
System.out.println("List: " + buildNumbers);
Enumeration e = Collections.enumeration(buildNumbers);
System.out.println("\nEnumeration over list:");
while (e.hasMoreElements()) {
Integer build = e.nextElement();
System.out.println("Value is: " + build);
}
}
}
The key thing to notice: there’s nothing “special” about the collection type. Any Collection works—List, Set, a Queue implementation, and so on.
Bridging a Legacy API Boundary (Where This Method Pays Off)
Most of the time, you won’t print elements. You’ll pass an Enumeration into something that expects it.
Example: Feeding a Legacy Consumer Without Polluting Your Codebase
Imagine you inherit a library that exposes this interface:
import java.util.Enumeration;
public interface LegacyNotifier {
void notifyRecipients(Enumeration recipients);
}
You want your modern code to work with List (or Set) and only convert at the call site. Here’s a full runnable demo with a minimal legacy implementation:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class LegacyBoundaryDemo {
interface LegacyNotifier {
void notifyRecipients(Enumeration recipients);
}
static class ConsoleLegacyNotifier implements LegacyNotifier {
@Override
public void notifyRecipients(Enumeration recipients) {
System.out.println("Sending notifications:");
while (recipients.hasMoreElements()) {
System.out.println("- " + recipients.nextElement());
}
}
}
public static void main(String[] args) {
List recipients = new ArrayList();
recipients.add("[email protected]");
recipients.add("[email protected]");
recipients.add("[email protected]");
LegacyNotifier notifier = new ConsoleLegacyNotifier();
// Adapter at the boundary: keep the rest of your code modern.
Enumeration enumeration = Collections.enumeration(recipients);
notifier.notifyRecipients(enumeration);
}
}
This is the pattern I recommend: treat Enumeration as an “edge type” you confine to integration layers.
The Reverse Bridge: When You Receive an Enumeration
Often the boundary goes the other direction: a legacy API returns an Enumeration, and you want a modern collection.
A standard companion method is:
Collections.list(Enumeration e)which returns anArrayList.
Here’s a short runnable example that round-trips:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationRoundTripDemo {
public static void main(String[] args) {
List regions = List.of("us-east", "us-west", "eu-central");
Enumeration e = Collections.enumeration(regions);
// Convert back to a normal List for stream processing, sorting, etc.
ArrayList copy = Collections.list(e);
System.out.println("Copy: " + copy);
System.out.println("Uppercase: " + copy.stream().map(String::toUpperCase).toList());
}
}
This is a clean way to quarantine legacy types and immediately bring them into the modern world.
Behavior That Surprises People: Mutability, Fail-Fast, and Ordering
If you treat Collections.enumeration() as “just iteration,” you can still get bitten by a few edge cases.
1) Structural modification during enumeration
Because the adapter is iterator-backed, modifying the underlying collection while iterating often triggers fail-fast behavior.
I see this bug when someone does “iterate and add missing items” in the same list. Here’s a runnable program that typically throws ConcurrentModificationException with ArrayList:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationFailFastDemo {
public static void main(String[] args) {
List services = new ArrayList();
services.add("api");
services.add("worker");
services.add("scheduler");
Enumeration e = Collections.enumeration(services);
try {
while (e.hasMoreElements()) {
String s = e.nextElement();
// Structural modification while iterating: usually fail-fast.
if ("worker".equals(s)) {
services.add("metrics");
}
System.out.println("Service: " + s);
}
} catch (RuntimeException ex) {
System.out.println("Caught: " + ex.getClass().getName() + " – " + ex.getMessage());
}
System.out.println("Final list: " + services);
}
}
Guidance I give teams:
- If you need to mutate, iterate over a copy.
- If you need to filter, build a new list of results.
- If you need concurrent mutation, use a data structure designed for it (more on that next).
2) Ordering depends on the collection
Enumeration doesn’t define ordering semantics. Your collection does.
ArrayListpreserves insertion order.HashSetdoes not promise stable iteration order.LinkedHashSetpreserves insertion order.
If the consuming legacy API assumes stable ordering, you should pass an ordered collection (or explicitly sort a copy before creating the enumeration).
Here’s a quick way I enforce stable ordering at the boundary:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class EnumerationOrderingDemo {
public static void main(String[] args) {
Set tags = new HashSet();
tags.add("priority:low");
tags.add("service:api");
tags.add("env:prod");
// If ordering matters to the consumer, sort a copy first.
List ordered = new ArrayList(tags);
Collections.sort(ordered);
Enumeration e = Collections.enumeration(ordered);
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
}
}
3) Null handling
Collections.enumeration() can enumerate collections containing null elements. Some legacy consumers choke on nulls, so I often sanitize at the boundary:
- Filter nulls into a new list.
- Fail fast with a validation check if null is a bug.
For example, if nulls are never valid, I’ll validate aggressively before adapting:
import java.util.Collection;
import java.util.Objects;
public class NullValidation {
public static void requireNoNulls(Collection c, String name) {
Objects.requireNonNull(c, name + " must not be null");
if (c.stream().anyMatch(Objects::isNull)) {
throw new IllegalArgumentException(name + " must not contain null elements");
}
}
}
4) Exceptions you’ll see
From the adapter itself, the big ones are:
NullPointerExceptionif you passnullfor the collection.NoSuchElementExceptionif the consumer callsnextElement()without checkinghasMoreElements().ConcurrentModificationException(runtime) when you structurally modify certain collections during iteration.
One subtle point: if you pass a collection whose iterator is not fail-fast (some concurrent collections), you might not get ConcurrentModificationException, but you may still get surprising logical outcomes (missing elements, seeing elements twice, etc.). “No exception” is not the same as “correct behavior.”
Thread Safety and Concurrency: What I Actually Do in Production
Enumeration is sometimes mistaken for being “thread-safe” because it’s old. It’s not.
What matters is the underlying collection and the rules of access around it.
Safe pattern: enumerate an immutable snapshot
If the legacy API is going to walk the enumeration later (or on another thread), I create a snapshot list first:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationSnapshotDemo {
public static void main(String[] args) {
List live = new ArrayList();
live.add("node-a");
live.add("node-b");
// Snapshot to avoid concurrent modification surprises.
List snapshot = List.copyOf(live);
Enumeration e = Collections.enumeration(snapshot);
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
// Mutating the live list after snapshot does not affect enumeration.
live.add("node-c");
System.out.println("Live: " + live);
}
}
This is the simplest thing that works for many systems.
Concurrent collections: when mutation during iteration is expected
If you truly have concurrent writes and reads, the right answer is usually to redesign the boundary so you don’t pass around a live view. But if you can’t, choose a data structure with iteration semantics that match your needs.
Two common options:
CopyOnWriteArrayList: Great for “many reads, few writes.” Iteration sees a snapshot at iterator creation time. Writes are more expensive.ConcurrentLinkedQueueorConcurrentHashMapviews: Iteration is weakly consistent (you might or might not see concurrent updates), but it won’t usually throw fail-fast exceptions.
The key is that Collections.enumeration() does not magically change those semantics; it reflects them.
Here’s a small, runnable example that demonstrates the “snapshot at iterator creation time” behavior with CopyOnWriteArrayList. Notice how the enumeration doesn’t blow up even though we add during iteration, but also notice that you can’t rely on seeing the new value:
import java.util.Collections;
import java.util.Enumeration;
import java.util.concurrent.CopyOnWriteArrayList;
public class EnumerationCopyOnWriteDemo {
public static void main(String[] args) {
CopyOnWriteArrayList items = new CopyOnWriteArrayList();
items.add("a");
items.add("b");
Enumeration e = Collections.enumeration(items);
while (e.hasMoreElements()) {
String v = e.nextElement();
System.out.println("Saw: " + v);
if ("a".equals(v)) {
items.add("c");
}
}
System.out.println("Final: " + items);
}
}
If you’re building infrastructure code where consistency matters (for example, sending config keys or generating a deterministic hash), I still prefer snapshotting and sorting, even with concurrent collections.
Using Collections.enumeration() in Real Application Boundaries
It’s easy to get stuck in toy demos, so here are a few “this actually happens” boundaries where Enumeration shows up, and how I adapt it without letting it infect the rest of the code.
1) Servlet/Jakarta request parameters
A common integration task is: read request parameter names from an Enumeration, then normalize/filter them, then pass them into something else.
I typically convert immediately to a List (or a Set if I need uniqueness), and then do my normal stream/loop work:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
public class RequestParamNormalizer {
// Pretend this comes from a servlet request.
static Enumeration incomingNames() {
List names = List.of("UserId", "debug", "traceId", "debug");
return Collections.enumeration(names);
}
public static void main(String[] args) {
Enumeration raw = incomingNames();
// Pull legacy Enumeration into a modern List immediately.
List normalized = new ArrayList();
for (String name : Collections.list(raw)) {
if (name == null) continue;
String trimmed = name.trim();
if (trimmed.isEmpty()) continue;
normalized.add(trimmed.toLowerCase(Locale.ROOT));
}
System.out.println(normalized);
}
}
That pattern is boring on purpose. It keeps the boundary clear.
2) ResourceBundle keys (older-style i18n surfaces)
Another place I see Enumeration in practice is resource bundle keys. Some APIs still return keys as an Enumeration. When I need to validate keys or generate diagnostics, I’ll do the same: convert to a list, then operate in modern code.
Even if you don’t personally use ResourceBundle, this is a useful pattern to remember: whenever an API gives you an Enumeration, the first step is often Collections.list(e).
3) “Legacy config consumer” patterns
A surprisingly common enterprise integration looks like this: some library wants Enumeration for “config keys” or “header names.” In your code, those values probably already exist as a Set or List. This is where Collections.enumeration() shines: minimal conversion, minimal disruption.
A Practical Adapter Utility I Actually Reuse
When Enumeration appears in more than one place, I don’t want a dozen call sites all doing Collections.enumeration(...) with slightly different snapshot/ordering/null rules. I’ll put a tiny adapter in a utility class.
Here’s the shape I like (with explicit policy decisions):
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
public final class Enumerations {
private Enumerations() {}
// Policy: snapshot + filter nulls, preserve original encounter order.
public static Enumeration fromCollectionSnapshot(Collection source) {
Objects.requireNonNull(source, "source");
List snapshot = new ArrayList(source.size());
for (T v : source) {
if (v != null) snapshot.add(v);
}
return Collections.enumeration(snapshot);
}
// Policy: no snapshot, pass-through (fastest, but unsafe if source can mutate).
public static Enumeration fromCollectionLive(Collection source) {
Objects.requireNonNull(source, "source");
return Collections.enumeration(source);
}
}
I’m explicit about the trade-off right in the method name. In code review, that prevents the “I thought enumeration was a snapshot” misunderstanding.
Bridging From Enumeration to Streams (Without Fancy Tricks)
Sometimes the “modern” pipeline you want is stream-based, but the legacy boundary gives you an Enumeration.
There isn’t a built-in Enumeration.stream() method, and that’s fine. For most business logic, the simplest approach is:
1) Convert to a list: List xs = Collections.list(e)
2) Stream it: xs.stream()
Example:
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class EnumerationToStreamDemo {
public static void main(String[] args) {
Enumeration e = Collections.enumeration(List.of(" api ", "", "worker", "api"));
List cleaned = Collections.list(e).stream()
.map(String::trim)
.filter(s -> !s.isEmpty())
.distinct()
.sorted()
.toList();
System.out.println(cleaned);
}
}
Why I like this approach:
- It’s easy to read in code review.
- It’s predictable (you have a concrete list).
- It naturally acts as a snapshot.
If the enumeration is huge and you truly need streaming without materializing a list, you can build a custom Spliterator or use Iterator bridging, but I only do that on hot paths where memory pressure is proven, not assumed.
Enumeration vs Iterator: A Mental Model That Prevents Bugs
A lot of the confusion comes from treating these as interchangeable. They’re similar, but the missing pieces matter.
Here’s how I keep it straight:
Enumerationis “read-only cursor”:hasMoreElements()+nextElement().Iteratoris “cursor with optional mutation”:hasNext()+next()+ (optional)remove().
Key implications:
1) If the algorithm needs to delete elements while iterating, Enumeration is the wrong tool.
2) If the algorithm needs to be stoppable/pausable and passed around, both can do that, but neither is inherently thread-safe.
3) If an API publishes Enumeration, it’s often a signal that the API is old or intentionally conservative. Treat it as a boundary, not a core abstraction.
Choosing Between Enumeration, Iterator, and Streams (With Specific Guidance)
I like to frame this as: keep the majority of your code modern, and only adapt at the edges.
Here’s the practical comparison I use with teams:
Traditional / Legacy-friendly
What I’d pick
—
—
Enumeration Collections.enumeration(myCollection)
Collection, adapt at boundary Collections.enumeration(...) at the call site
Enumeration cannot remove
Iterator with remove() (when supported), or build filtered copy Iterator or copy
Manual loops
map / filter / collectors Streams for clarity, loops for hot paths
Vector/Hashtable conventions
java.util.concurrent types + snapshots Snapshot first, concurrent types only when needed
Enumeration back to List Manual loop
Collections.list(e) Collections.list(e)Specific recommendations (not vague “it depends” advice):
- If a method signature demands
Enumeration, create it withCollections.enumeration()and keep it local. - If you control the API, don’t publish new methods that accept
Enumeration. UseIterableorStream. - If you need mutation while iterating, don’t reach for
Enumeration. UseIteratoror redesign the algorithm.
Performance Notes and Real-World Tradeoffs
I don’t reach for micro-benchmarks unless iteration is on a critical path, but it helps to know what you’re paying for.
Allocation and overhead
Collections.enumeration(collection) creates a small wrapper object around an iterator. In practice, this overhead is usually negligible compared to whatever you’re doing with the elements.
The bigger performance decisions are rarely about the wrapper object. They’re about whether you:
- iterate a live collection or a snapshot copy,
- sort/deduplicate/filter before adapting,
- allocate intermediate lists to bridge to streams.
As a rough mental model:
- Live enumeration is the lowest-allocation option, but it’s easiest to misuse when the collection can mutate.
- Snapshot enumeration costs O(n) time and O(n) memory for the snapshot, but it gives you deterministic behavior.
- Round-tripping via
Collections.list(e)is another O(n) allocation; it’s often worth it for readability and safety.
“Fastest” vs “most predictable”
In real systems, the expensive failure mode isn’t “we allocated a list.” It’s “we threw ConcurrentModificationException in production because a background refresh mutated the list while a legacy consumer was enumerating.”
If I’m unsure about mutation, I default to snapshotting. If I’m sure it’s an immutable collection (or it’s confined to one thread and never mutated during iteration), I use a live enumeration for simplicity.
Hot-path tuning (when you should actually care)
If this adaptation runs millions of times per minute, I’ll consider:
- eliminating repeated conversions by caching the snapshot or passing a
Listinstead, - reducing churn by reusing collections in tight loops (carefully),
- avoiding streams if profiling shows it matters.
But I only do this after profiling. On most codebases, correctness and maintainability dominate.
Common Pitfalls (And How I Avoid Them)
These are the mistakes I most often see around Collections.enumeration().
Pitfall 1: Assuming it’s a snapshot
It’s not. It’s iterator-backed. If the source collection changes structurally while enumerating, fail-fast collections can throw.
How I avoid it: snapshot when the source might mutate.
Pitfall 2: Passing Enumeration deeper into your code
Once Enumeration leaks into core services, suddenly everything becomes more awkward: you can’t use enhanced for directly, streams become clunky, and you end up with adapter code everywhere.
How I avoid it: convert at the boundary. Either:
Collections.enumeration(collection)right before calling the legacy method, orCollections.list(enumeration)right after receiving it.
Pitfall 3: Using raw types and losing generics
Legacy APIs sometimes use raw Enumeration. If you’re not careful, you’ll get unchecked warnings and maybe runtime ClassCastException.
How I avoid it: isolate the unchecked cast in a single place and validate early.
Example pattern:
import java.util.Enumeration;
public class LegacyCasts {
@SuppressWarnings("unchecked")
public static Enumeration asStringEnumeration(Enumeration e) {
return (Enumeration) e;
}
}
I don’t love unchecked casts, but I love them even less when they’re scattered across the codebase.
Pitfall 4: Forgetting ordering requirements
If you pass a HashSet into Collections.enumeration(), the consumer sees whatever iteration order the set chooses. That can break code that expects deterministic behavior.
How I avoid it: use List, LinkedHashSet, or sort a copy.
When I Avoid Collections.enumeration() Entirely
Even though it’s handy, there are times I intentionally don’t use it.
1) When I control the API
If it’s my library or my service boundary, I won’t accept Enumeration as an input type. I’ll use:
Iterableif I want maximum flexibility,Collectionif I need size/containment operations,Streamif I want to clearly express one-pass processing.
If I need legacy compatibility, I’ll offer a dedicated adapter method rather than making Enumeration the “main” API.
2) When the consumer expects to traverse multiple times
Enumeration is one-pass. Some legacy code incorrectly assumes it can iterate twice by reusing the same enumeration instance.
If you see code like:
Enumeration e = …;
consume(e);
consume(e);
That’s a bug unless consume resets the enumeration somehow (it can’t). In these cases, I prefer to pass a List or Supplier<Enumeration> if I’m stuck with the signature.
3) When correctness depends on a stable view
If I’m enumerating “the set of recipients,” “the current config keys,” or anything that should be logically consistent within an operation, I take a snapshot. If snapshotting is required anyway, I’ll usually return an enumeration of the snapshot (via Enumerations.fromCollectionSnapshot(...)) rather than enumerating a live structure.
Mini-FAQ
Is Collections.enumeration() deprecated?
No. It’s just old. It exists because the JDK still has APIs that speak Enumeration, and there are plenty of codebases that still need a bridge.
Does Collections.enumeration() work for Set and Queue?
Yes. Any Collection works. Just remember that ordering and concurrency behavior come from the underlying collection.
Can I remove elements while iterating?
Not through Enumeration. If you need removal, use an Iterator (when supported) or build a new filtered collection.
What if I need an enumeration over an array?
Convert the array to a list first (for example, List.of(array) or Arrays.asList(array)), then adapt. If you need a snapshot, create a new ArrayList(Arrays.asList(array)).
How do I debug ConcurrentModificationException here?
Treat it like you would with any iterator-driven loop. Look for structural modifications to the underlying collection (adds/removes/clear) while the enumeration is being consumed. If you can’t guarantee immutability, snapshot.
A Simple Rule That Keeps My Codebase Clean
When I need to use Enumeration, I follow one rule:
- keep it at the edges, convert immediately, and choose explicitly between live vs snapshot.
That’s why Collections.enumeration() is still useful: it lets you satisfy legacy method signatures without dragging legacy iteration semantics through the rest of your application.


