Why Casting a Generic Array to Object[] Dodges the ClassCastException
Same method, same object, called twice: one call returns fine, the other throws. The difference is a cast you never wrote.
Here is a generic Box<E> whose toArray() returns E[]. Since Java will not let you write new E[10], the method keeps an Object[] internally and casts it on the way out. Call it once and assign the result to an Object[], and it prints 2. Call it again and read .length directly, and it dies with ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String;.
public class Box<E> {
private Object[] data = new Object[] { "a", "b" };
@SuppressWarnings("unchecked")
public E[] toArray() {
return (E[]) data;
}
public static void main(String[] args) {
Box<String> box = new Box<String>();
Object[] a = box.toArray(); // works fine
System.out.println(a.length); // prints 2
int n = box.toArray().length; // ClassCastException
}
}The developer who posted this to Stack Overflow already understood type erasure, and went to the bytecode to look for the difference. There it was: before arraylength, the compiler had inserted a checkcast to String[]. That makes no obvious sense, because arraylength works on any array and does not care about the element type. So why is the cast there, and why does an Object[] variable get away without it?
The short answer: the array was never a String[]. The (E[]) cast was a lie the compiler could not check, and the hidden checkcast is the point where Java catches up with it. Assigning to Object[] works only because, at that one site, the lie and the truth happen to agree.
Key takeaways
- After erasure,
E[] toArray()compiles to a method returningObject[], and the array it hands back really is anObject[]. - When the result is used as a
String[], javac inserts acheckcast, even for operations like.lengththat would work without it. - The Java Language Specification deliberately enforces the cast almost everywhere rather than only where strictly needed, to keep the spec simple and fail fast after heap pollution.
- The fix is to build a genuinely typed array: let the caller pass
String[]::newornew String[0], pass aClassobject, or return aList.
What erasure leaves of E[]
Java generics exist at compile time. Once javac has checked your types, it erases each type variable to its bound, which for an unbounded E is Object. The signature you wrote, E[] toArray(), becomes a method whose bytecode descriptor is ()[Ljava/lang/Object;, meaning no arguments, returns an array of Object. That is the descriptor visible in the questioner's invokevirtual instruction.
The field is just as plain. new Object[] { "a", "b" } creates an array whose runtime class is Object[], which the JVM spells [Ljava.lang.Object;. It holds two strings, but that does not make it a string array. Array types in Java are reified: every array carries its component type with it at runtime, and no cast changes that.
So return (E[]) data; converts nothing. With E erased to Object, it is a cast from Object[] to Object[], and the compiler flags it as unchecked because it cannot verify the claim. The @SuppressWarnings("unchecked") annotation hides that warning. The warning was right.
The cast you never wrote
Erasure removes type information from the method, but the caller still believes it. In main, box is a Box<String>, so the expression box.toArray() has the static type String[]. The method itself can only promise Object[]. To make the caller's view true, javac inserts a cast at the call site, and that is what the questioner found:
invokevirtual toArray:()[Ljava/lang/Object;
checkcast class "[Ljava/lang/String;"
arraylengthThe checkcast asks the JVM whether the object on the stack is really a String[]. It is an Object[], so the JVM throws, and the error message names both array classes in their internal forms, [Ljava.lang.Object; and [Ljava.lang.String;.
The first call behaves differently because of where the value goes. The erased method returns an Object[], and the variable a is declared Object[]. The value already fits the destination, so there is nothing to check, and the questioner's bytecode shows no cast on that path. Printing a.length then works on a plain Object[]. The cast to Object[] does not prevent the exception so much as avoid the one place the exception would be raised.
Why .length gets checked anyway
The questioner's sharpest objection is that the cast is pointless: arraylength would happily measure an Object[]. A compiler could, in principle, notice that and skip the check. The top-voted answer agrees it is possible and explains why Java does not bother. The rules of generics say the cast is enforced almost any time you interact with the result of an expression whose type comes from a type variable. They do not say "only when the operation needs it".
The reason is what the cast is for. Getting a wrong-typed value past the compiler, by suppressing unchecked warnings and lying with a cast, is heap pollution, and the answer's view is that this is the programmer's problem. The spec handles it with two goals: you cannot cause memory corruption or a core dump this way, and the behaviour is otherwise as simple as possible for the people maintaining compilers. Intentionally polluting the heap is not a designed way to use Java.
There is also a practical upside. If heap pollution has happened, you usually want the ClassCastException as early as possible, close to the code that caused it, even when the operation in hand would have survived. A check that fires on .length today is better than one that fires three classes away next week.
Arrays lie one way, generics the other
Part of what makes this confusing is that Java has two type systems for containers, and they fail in opposite directions. Arrays are covariant: you may treat a String[] as an Object[]. That is itself a small lie, and the JVM polices it at runtime. Put an InputStream into an Object[] variable that actually points at a string array, and the store fails immediately with ArrayStoreException.
Generics work the other way round. The compiler does the checking, and the runtime knows nothing. If you get past the compiler with casts and suppressed warnings, nothing fails at the moment of the lie. A ClassCastException turns up later, wherever javac happened to insert a cast. The Box example mixes the two systems, which is why it is so hard to reason about.
The answer is blunt about the array side: covariant array casting exists to paper over pre-generics code, a gift to code that is now around 25 years old, and its existence is no reason to build new APIs on it.
Covariant arrays
A String[] can be referenced as an Object[].
- Checked by the JVM on every store
- Wrong element raises
ArrayStoreException - Fails at the bad write
Erased generics
E[] becomes Object[] after compilation.
- Checked by casts javac inserts
- Wrong type raises
ClassCastException - Fails later, at a use site
The page count of a specification
Could the Java Language Specification have spelled out exactly when a cast is needed, so that box.toArray().length just worked? Yes, and the answer's argument for why it did not is about cost rather than possibility:
- Heap pollution is outside the design. The spec was never meant to make sense when you corrupt the heap on purpose. It only needs to guarantee safety and make compliance testable.
- Specs are expensive. The JLS is among the largest specifications of any popular language. Every rule must be tested and maintained, and if a shorter text covers the language unambiguously, the shorter text wins.
- The payoff is tiny. Making the questioner's snippet work would be worth, the answer estimates, at most a single line of spec, because the situation should be so rare.
That reframes the question. The Object[] variable is not a technique that defeats the check; it is an accident of where the check falls. A spec that inserts casts broadly does not promise any particular site stays cast-free, so code that depends on dodging the cast depends on something nobody guaranteed.
Build the array the caller actually wants
Every fix comes down to making the array honest: its runtime class should really be String[] when the caller expects one. The cleanest way is to let the caller, who knows the concrete type, make it. This is how Java streams handle toArray:
public E[] toArray(IntFunction<E[]> arrayMaker) {
E[] out = arrayMaker.apply(data.length); // really the right type
System.arraycopy(data, 0, out, 0, out.length);
return out;
}
String[] s = box.toArray(String[]::new);The older strategy, used by java.util.List, takes an array instead of a factory and grows it if it is too small. One detail: the snippet in the answer calls a.getClass(), but the parameter is named target; the version below uses the parameter.
@SuppressWarnings("unchecked")
public E[] toArray(E[] target) {
if (target.length < data.length)
return (E[]) Arrays.copyOf(data, data.length, target.getClass());
System.arraycopy(data, 0, target, 0, data.length);
return target;
}
String[] s = box.toArray(new String[0]);Do not bother sizing that argument. The answer recommends passing new String[0] and leaving the copy to the method, noting that the JVM is very good at detecting and eliminating the zero-length allocation.
| Approach | Caller writes | Catch |
|---|---|---|
| Caller supplies a factory | box.toArray(String[]::new) | Recommended; same shape as streams |
| Caller supplies an array | box.toArray(new String[0]) | Method needs a copy fallback |
Reify with a Class object | new Box(String.class) | Cannot express Box<List<String>> |
| Return a list | box.toList() | No array at all |
Limits, open questions and what to watch
The reified option has a real ceiling. Passing String.class at construction lets the class build a correct array, but generic element types break it: a Box<List<String>> has no Class object that means List<String>. If your container must hold parameterised types, the caller-supplied factory or array is the safer design. The answer's strongest position goes further: non-primitive arrays are low-level constructs that an API should avoid, so toArray() should become toList(). Not everyone will accept that when an existing interface or a library call demands an array, and the factory pattern is the compromise.
The other thing to watch is the temptation to keep the original method and tell callers to assign to Object[]. It works in the questioner's example, but only because that site needed no cast. Any caller that uses the result as a String[], starting with String[] s = box.toArray();, gets the exception, and because the spec inserts casts broadly rather than minimally, the safe sites are not something to map and rely on.
Why can't Java just let me write new E[10]?
Because of type erasure there is no E at runtime, so the JVM has no component type to build the array with. Arrays need their real element type when they are created, which is why the fixes all bring the type in from the caller.
Does @SuppressWarnings("unchecked") cause the ClassCastException?
No. It only silences the compiler warning about the (E[]) cast. The exception comes from the hidden checkcast javac inserts where the result is used as a String[].
Is it safe to assign the result to Object[] as a workaround?
Only in the narrow sense that the questioner's line ran. Any use of the value as the generic type can still throw, and the spec does not promise casts appear only where an operation needs them.
Why does .length trigger a cast when any array has a length?
The rules of generics enforce the cast almost any time you interact with a type-variable-derived expression. Making the check depend on the operation would add spec complexity for a case that only arises after heap pollution.
This article draws on Why does casting to Object[] prevent a ClassCastException? and answers to it (1, 2), on the Stack Exchange network, licensed under CC BY-SA 4.0.
- #ClassCastException
- #casting
- #checkcast
- #generics
- #java
- #type-erasure


Comments