🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
// 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)
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);
}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();
}
}
}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());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
}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
javax.lang.model arrives with processors; singular MirroredTypeException leads.
Plural MirroredTypesException.getTypeMirrors() added for list-valued members.
Module-aware processor classpaths; the round model itself unchanged.
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.