Java erases most generic type arguments from runtime class identity. A Class token can validate each element when an unchecked external list crosses into a typed API.
Java type erasure: validate elements with an explicit Class token
Operational contract
The method accepts a List<?> from an untyped integration boundary and a requested element class. Class.cast checks each value and creates a fresh List<T>. It rejects null input and null elements before List.copyOf, which itself does not allow null members. The token checks the runtime class of each element; it cannot validate a nested type such as List<String> inside a List<List<String>>. A wire-format parser must still validate field meaning, range, and schema. Two overloads that differ only by List<String> versus List<Long> are illegal because their erased signatures collide.
Failure case
A plug-in returns 23 raw receipt values, and the application expects String IDs. The first Long value fails at the boundary with ClassCastException rather than much later inside a billing routine. Calling getClass on the list itself would reveal its implementation class, not its erased element argument.
Java code
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class TypedReceiptBoundary {
public static <T> List<T> checkedCopy(List<?> incoming, Class<T> elementType) {
Objects.requireNonNull(incoming);
Objects.requireNonNull(elementType);
if (incoming.size() > 47) throw new IllegalArgumentException("Too many values");
List<T> checked = new ArrayList<>(incoming.size());
for (Object value : incoming) checked.add(elementType.cast(value));
return List.copyOf(checked);
}
}Performance and ownership cost
Validation and copying take O(N) time and O(N) new list storage for N values, capped at 47. Class.cast checks each runtime element; the one-time copy also prevents later mutations of the incoming list from changing the returned membership.
Common Mistakes
- Do not expect a List instance to retain its String or Long type argument at runtime.
- Do not use Class<T> to claim validation of nested generic arguments.
- Do not defer unchecked casts until after data enters domain logic.
Connected lessons
- Java generics: invariance, bounds, and type erasure
- Java reflection: inspect types without breaking contracts
- Java generic varargs: audit the hidden array before using SafeVarargs
- Java generic wildcards: copy from a producer into a consumer
- Java bridge methods: preserve overrides after generic erasure
- Java generics and language contracts quiz
- Advanced Java
