🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
at com.devinhyderabad.inventory.WarehouseMain.main(WarehouseMain.java:6)⚡ Quick Fix Works 80% of the time
Stop aliasing arrays through supertype references — or instanceof-check every write when a legacy API forces the pattern.
String[] shelf = new String[3];
shelf[0] = String.valueOf(42); // store Strings, or nothing at all
// Forced supertype aliasing? Guard writes:
if (!(value instanceof String)) {
throw new IllegalArgumentException("shelf holds Strings only");
}
((Object[]) shelf)[0] = value;🧠 Why this Happens
Tap to expand the deep technical explanation
Arrays carry their component type at runtime — unlike erased generics — and every aastore instruction compares the VALUE's klass against that component type, throwing ArrayStoreException on mismatch (naming the value's class, not the array's!). Java made array references covariant (String[] IS-A Object[]) so pre-generics APIs like Arrays.sort(Object[]) could work; covariance makes the ALIASING legal while making some writes necessarily illegal — an accepted unsoundness. Generics were introduced invariant precisely because this demo compiles yet cannot be made safe: the compile step must reject what the runtime would have to.
The HITEC City Parking Spot Analogy:
A crate whose manifest says TOOLS gets handed to you as an unlabeled box (Object[]): stuffing in a watermelon passes the doorway (compile), but the moment it lands, the shelf inspector reads the real manifest and ejects the watermelon by name.
🔁 How to Reproduce Confirm this is your error
Object[] shelf = new String[3]; shelf[0] = Integer.valueOf(42); — compiles cleanly (the STATIC type Object[] accepts any Object) and throws instantly at the implicit store-check. Captured byte-identical on OpenJDK 17 and Temurin 25; message is always just the stored object's runtime class.
🛠️ Solutions (5 Ways to Fix)
Replace aliased Object[] with typed collections
👉 Use this as the structural fix — make the mistake impossible instead of caught.
List<Object> bad = new ArrayList<String>() does NOT compile because generics are invariant BY DESIGN — precisely to close this array hole. Moving shared array APIs to typed Lists turns the runtime bomb into a red squiggle.
// Compile-time rejection — the entire point of invariance:
// List<Object> bad = new ArrayList<String>(); // does not compile
List<String> shelf = new ArrayList<>();
shelf.add(String.valueOf(42)); // wrong-type bugs now visible at compile timeinstanceof-guard writes through supertype aliases
👉 Use this when an old API hands you Object[] that is really something stricter underneath.
When refactoring is impossible (third-party sinks, serialized shapes), validate the element against the REAL component type immediately before each write and fail with a domain error naming both types — far better diagnostics than ASE at some distant read site.
void store(Object[] target, Object value) {
if (!(value instanceof String)) {
throw new IllegalArgumentException(
"target holds Strings, got " + value.getClass().getName());
}
target[0] = value;
}Mind hidden covariance bridges: varargs and System.arraycopy
👉 Use this when the stack shows no obvious array write of yours.
Passing String[] where Object... is expected aliases silently; System.arraycopy(Object[] src ...) into a stricter destination array performs checked stores that throw ASE far from your logic. Reflection-built arrays (Array.newInstance(componentType, n)) filled from JSON rows are the other classic offender.
void bridge(Object... items) { // caller passes String[]
Object[] copy = new String[items.length];
System.arraycopy(items, 0, copy, 0, items.length); // ASE if an Integer slips in
}Convert per-element at reflection boundaries
👉 Use this for frameworks materializing arrays from external data.
Array.newInstance(componentType, length) plus Array.set performs the same runtime check; convert each incoming value to the declared component type BEFORE set(), so failures name the row and converter rather than a bare ASE.
Object arr = Array.newInstance(componentType, rows.size());
for (int i = 0; i < rows.size(); i++) {
Array.set(arr, i, converters.get(componentType).apply(rows.get(i)));
}Catch-and-translate ONLY at adapter seams (DEV NOTE)
👉 Use this sparingly, at single legacy interop points — never as general handling.
Swallowing ASE elsewhere hides real type bugs; but one translation at a plugin seam, rethrown with context about target and offending types, keeps legacy integrations debuggable.
try {
sink.accept(values);
} catch (ArrayStoreException e) {
throw new IllegalArgumentException(
"values incompatible with declared component type", e);
}📋 Version Notes
Semantics identical since Java 1.0 — the message is just the stored object's runtime class name, nothing more.
Unchanged; generics-era application code rarely hits it outside varargs and reflection bridges.
Captured byte-identical against JDK 25 runtimes — truly version-stable.
Unchanged; records, sealed types, and pattern matching do not alter array covariance rules.
🛡️ How to Prevent This Next Time
Prefer typed Lists over shared arrays across API boundaries, avoid Object[] aliases of specific-typed arrays entirely, guard reflective array construction with per-element conversion, and flag any Object[] parameter as suspicious in code review.