🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG// DOC-DERIVED — occurs inside Processor.process() when reading Class-valued annotation members. // Frame details vary by javac build; the message text is stable. javax.lang.model.type.MirroredTypesException: Attempt to access Class objects for TypeMirrors at com.sun.tools.javac.code.Type$AnnotationMirror... (javac internals) at com.devinhyderabad.processor.MapperProcessor.visit(MapperProcessor.java:58) at jdk.compiler/org.openjdk.source... (round dispatch)

⚡ Quick Fix Works 80% of the time

Catch the exception and pull the mirrors out of it — the API throws it BY DESIGN carrying exactly what you asked for.

try { Class<?> target = route.targetClass(); // throws here } catch (MirroredTypesException e) { List<? extends TypeMirror> mirrors = e.getTypeMirrors(); TypeMirror targetMirror = mirrors.get(0); // symbolic reference, safe mid-round }

🧠 Why this Happens

Tap to expand the deep technical explanation

During processing rounds, referenced classes may not be compiled YET — materializing live Class objects would force premature loading and break the round model javac depends on. So lang-model hands back symbolic TypeMirrors describing the types, and reflective accessors on annotations deliberately THROW MirroredTypesException (or MirroredTypeException for single members) carrying those mirrors in getTypeMirrors()/getMirroredType(). It is an API contract, not corruption: the exception doubles as the getter. Complication to respect — a mirror whose kind is TypeKind.ERROR means the referenced type failed to resolve THIS round; treat it as absent and defer rather than generating code against it.

The HITEC City Parking Spot Analogy:

Blueprint review before construction: the architect hands you the DRAWING of the steel beam, not the beam itself — demanding the real beam mid-review gets you escorted back to drawings.

🔁 How to Reproduce Confirm this is your error

Write a Processor reading @Route(target = Activity.class) via annotation.targetClass() inside process(). DOC-DERIVED — processor harness skipped per budget.

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Use the documented catch-and-extract pattern

👉 Use this when/if you want the quickest sanctioned read of a Class-valued member inside process().

The Javadoc blesses this idiom: call the accessor optimistically, catch MirroredTypesException, and retrieve getTypeMirrors() from the caught instance. The guard throw after the accessor is NOT dead-code paranoia - MirroredTypesException is unchecked, so a normal return here would mean annotations are being read OUTSIDE a processing round, which is itself the bug worth failing on loudly. Clunky-looking, officially supported, and immune to round-model changes.

TypeMirror targetType; try { route.targetClass(); // mid-round this accessor is DESIGNED to throw throw new IllegalStateException( "targetClass() resolved - annotation read outside a round?"); } catch (MirroredTypesException e) { targetType = e.getTypeMirrors().get(0); }
Solution 2

Traverse AnnotationMirror values with a TypeVisitor

👉 Use this when/if you already iterate annotation mirrors generically (framework-grade processors).

Element.getAnnotationMirrors() returns mirrors whose getElementValues() map exposes AnnotationValue entries; a simple visitor extracts TypeMirror instances WITHOUT any throwing accessor — the cleanest path for processors handling many annotation types uniformly.

for (AnnotationMirror am : element.getAnnotationMirrors()) { for (var entry : am.getElementValues().entrySet()) { if (entry.getKey().getSimpleName().contentEquals("target")) { TypeMirror tm = (TypeMirror) entry.getValue().getValue(); } } }
Solution 3

Design annotations storing String names instead of Class members

👉 Use this when/if you OWN the annotation and can sidestep the whole mirror dance.

@Route(target = "com.app.OrderActivity") reads as plain String everywhere — no proxies, no exceptions, no ERROR kinds. Pair with Elements.getTypeElement(CharSequence) inside the processor to resolve names to elements on demand.

@interface Route { String targetClassName(); // FQN string — mirror-proof by design } TypeElement te = elements.getTypeElement(route.targetClassName());
Solution 4

Handle TypeKind.ERROR — defer or warn

👉 Use this when/if extracted mirrors sometimes arrive broken because referenced classes fail compilation this round.

A mirror with kind ERROR signals the referenced type did not resolve yet (or never will). Check kind before generating code: defer to the NEXT processing round when possible, otherwise emit a Messager warning pinpointing the offending annotation.

if (tm.getKind() == TypeKind.ERROR) { messager.printMessage(Diagnostic.Kind.WARNING, "@Route target unresolved: " + tm, element); return; // let later rounds retry }
Solution 5

Unit-test processors with Google Compile Testing

👉 Use this when/if mirror-related regressions only appear during full builds.

compile-testing compiles snippets against your processor in-memory, asserting generated output and diagnostics. Mirror bugs surface in milliseconds locally instead of as mysterious CI failures — cover both happy path and ERROR-kind cases.

Compilation comp = Compiler.javac() .withProcessors(new MapperProcessor()) .compile("import com.devinhyderabad.route.Route;", "@Route(targetClass = OrderActivity.class) class X {}"); assertThat(comp).hadErrorCount(0);

📋 Version Notes

Java 6

javax.lang.model arrives with processors; singular MirroredTypeException leads.

Java 8

Plural MirroredTypesException.getTypeMirrors() added for list-valued members.

Java 11

Module-aware processor classpaths; the round model itself unchanged.

Java 21

Same semantics — mirrors stay symbolic mid-round by design.

🛡️ How to Prevent This Next Time

Prefer String-named members in new annotations, standardize mirror extraction in one shared processor utility, treat TypeKind.ERROR as a first-class case in codegen, and give every processor compile-testing coverage before release.