Built-in and custom annotations, retention and targets, annotation processing and Lombok, the Reflection API, modules and setAccessible, MethodHandles, dynamic proxies, runtime generics, framework usage, security and GraalVM native image.
Theory
Q1
What is an annotation in Java and what can it do?
basic
An annotation is typed metadata attached to a program element (class, method, field, parameter, type use, package, module). It has no behaviour by itself; a compiler, tool or runtime library reads it and acts.
Compile time: @Override, @FunctionalInterface are checked by javac.
Runtime: frameworks read them reflectively (Spring, JPA, JUnit).
⚠ Follow-up traps
Does an annotation change how the annotated method executes? No. Something else must read it.
Can an annotation extend another annotation? No. Annotation types implicitly extend java.lang.annotation.Annotation and cannot extend anything.
#annotations#basics
Q2
Name the important built-in annotations.
basic
@Override, @Deprecated, @SuppressWarnings, @FunctionalInterface and @SafeVarargs are the core ones in java.lang.
@Override: compile error if the method does not override or implement a supertype method.
@Deprecated(since="9", forRemoval=true): forRemoval produces a stronger warning.
@SuppressWarnings("unchecked"): silences named compiler lints, keep the scope narrow.
@FunctionalInterface: compile error if the interface has other than one abstract method.
@SafeVarargs: asserts a generic varargs method is heap-pollution safe.
⚠ Follow-up traps
Is @FunctionalInterface required to use a lambda? No, it is only a compile-time guard.
Does @Deprecated stop the code from compiling? No, only warns; forRemoval=true warns louder but still compiles.
#built-in#override#deprecated
Q3
How do you declare a custom annotation?
basic
Use @interface. Members are declared like abstract methods without parameters, may have default values, and are limited to primitives, String, Class, enums, annotations and one-dimensional arrays of those.
Can a member be Object or List<String>? No, only the permitted types.
Can a member default to null? No, null is not a legal annotation value.
#custom#declaration
Q4
Explain RetentionPolicy SOURCE, CLASS and RUNTIME.
basic
@Retention decides how long an annotation survives. SOURCE is discarded by the compiler, CLASS (the default) is written to the .class file but not available through reflection, RUNTIME is kept in the class file and visible via reflection.
@Override is SOURCE; Spring's @Component is RUNTIME.
CLASS is useful for bytecode tools (e.g. some static analyzers, ASM-based agents).
⚠ Follow-up traps
What is the default retention if you omit @Retention?CLASS, so getAnnotation returns null at runtime.
Why does my custom annotation not show up in reflection? Almost always missing @Retention(RUNTIME).
#retention#meta-annotations
Q5
What does @Target do and what ElementTypes exist?
basic
@Target restricts where an annotation may be applied. Values include TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, MODULE, RECORD_COMPONENT.
Without @Target, an annotation applies to all declaration contexts (type-use contexts included since Java 14).
TYPE_USE allows List<@NonNull String> and casts.
Misplacing it is a compile error.
⚠ Follow-up traps
Does TYPE cover annotation types? Yes, it covers classes, interfaces, enums and annotation types; ANNOTATION_TYPE covers only annotation types.
Can a field annotation also apply to a getter? Not automatically; only records and Kotlin apply propagation rules.
#target#element-type
Q6
What are meta-annotations?
basic
Meta-annotations are annotations applied to annotation declarations: @Retention, @Target, @Documented, @Inherited, @Repeatable.
@Documented includes the annotation in Javadoc.
@Inherited lets a class annotation be inherited by subclasses.
@Repeatable allows the same annotation more than once via a container annotation.
⚠ Follow-up traps
Is @Retention itself annotated? Yes, with @Retention(RUNTIME) and @Target(ANNOTATION_TYPE).
Is @Inherited honoured on interfaces? No, only class inheritance via extends.
#meta-annotations
Q7
How does @Inherited behave exactly?
intermediate
If a runtime annotation is meta-annotated @Inherited and placed on a class, Class.getAnnotation on a subclass finds it by walking up the superclass chain. It does not apply to methods, fields, interfaces or interface implementations.
getDeclaredAnnotations never includes inherited ones; getAnnotations does.
Spring's AnnotatedElementUtils/MergedAnnotations search interfaces and methods as well, which core reflection does not.
⚠ Follow-up traps
Are annotations on an overridden method inherited by the override? No. Core reflection does not look at the overridden method.
Are annotations on the interface a class implements visible via getAnnotation? No.
#inherited#reflection
Q8
How do repeatable annotations work?
intermediate
Java 8 added @Repeatable(Container.class). The compiler wraps repeated annotations into the container annotation, whose value() returns an array of the repeatable type.
@Retention(RetentionPolicy.RUNTIME) @Repeatable(Roles.class)@interface Role { String value(); }@Retention(RetentionPolicy.RUNTIME)@interface Roles { Role[] value(); }@Role("admin") @Role("auditor")class Report {}// Report.class.getAnnotationsByType(Role.class).length == 2// Report.class.getAnnotation(Role.class) == null (they live in @Roles)
⚠ Follow-up traps
Why does getAnnotation(Role.class) return null with two @Role? The compiler stored them inside @Roles; use getAnnotationsByType.
Must the container have the same or longer retention? Container retention must be at least as long as the repeatable's; target must be a subset-compatible.
#repeatable#java8
Q9
How do you read annotations with reflection?
basic
AnnotatedElement (implemented by Class, Method, Field, Constructor, Parameter) offers isAnnotationPresent, getAnnotation, getAnnotations, getDeclaredAnnotations and getAnnotationsByType.
Method m = Service.class.getMethod("run");Timed t = m.getAnnotation(Timed.class);if (t != null) System.out.println(t.name() + " " + t.warnAboveMs());
The returned object is a JDK-generated dynamic proxy implementing the annotation interface.
Parameter names are only real if compiled with -parameters.
⚠ Follow-up traps
What is the returned annotation object's class? A Proxy class (e.g. $Proxy12), not a user class.
Do annotations on parameters via getParameterAnnotations line up with getParameters? Yes by index, but synthetic/enum constructor parameters can shift counts.
#reflection#annotations
Q10
What are the rules for annotation member values?
intermediate
Values must be compile-time constants: literals, constant expressions, Class literals, enum constants, nested annotations, or arrays of these. A single-element value can be written without its name.
@SuppressWarnings("x") is shorthand for @SuppressWarnings(value = "x").
Single-element arrays can omit braces: @Target(ElementType.FIELD).
static final String constants are fine, but a non-constant (System.getenv()) is not.
⚠ Follow-up traps
Can I use a runtime-computed string such as a config property? No. Frameworks resolve placeholders themselves (@Value("${x}")).
Does changing an annotation default break binary compatibility? Callers already compiled read the default at runtime, so they see the new default.
#annotations#constants
Q11
How does an annotation processor (APT) work?
intermediate
javac discovers Processor implementations (via META-INF/services/javax.annotation.processing.Processor or --processor-path) and runs them in rounds. Each round the processor sees elements annotated with its supported annotations and may generate new source or class files, which trigger further rounds until no new files appear.
Processors read the model through javax.lang.model (Element, TypeMirror), not reflection, because classes are not loaded yet.
Generate files with Filer; report problems with Messager.
It runs at build time, so there is no runtime cost.
⚠ Follow-up traps
Can a processor modify an existing source file? Not officially; it can only add new files. Lombok uses internal javac APIs to alter the AST.
Why is Gradle annotationProcessor configuration separate from implementation? Since Java 9/Gradle 5, processors on the compile classpath are not auto-run; they must be explicit to keep builds fast and reproducible.
#apt#annotation-processing#javac
Q12
How does Lombok work under the hood?
advanced
Lombok registers as an annotation processor but, instead of only generating new files, it hooks into javac's (or ECJ's) internal AST and mutates the parsed tree of your class to add getters, constructors, equals, builders and so on before bytecode generation.
It relies on non-public compiler internals, so each new JDK often needs a Lombok upgrade.
IDEs need a plugin because they use their own compiler front ends.
delombok produces plain Java if you need to drop it.
⚠ Follow-up traps
Is Lombok a runtime library? No, compileOnly/provided scope suffices; the generated bytecode has no dependency.
Why can Lombok see @Getter fields but not generated methods in other processors? Processor ordering and rounds; a processor does not see another's AST mutations reliably, so MapStruct needs lombok-mapstruct-binding.
#lombok#apt#ast
Q13
What are the common Lombok pitfalls?
intermediate
Generated equals/hashCode/toString can be dangerous on JPA entities, and some annotations hide design problems.
@Data on an entity includes lazy collections in toString, triggering lazy loading or LazyInitializationException.
@EqualsAndHashCode on all fields breaks when a mutable or generated ID field changes in a HashSet.
@Builder ignores field initializers unless @Builder.Default is used.
@AllArgsConstructor order depends on field order; reordering fields silently changes the constructor.
⚠ Follow-up traps
Does @Builder + field initializer List<X> xs = new ArrayList<>() work? The builder's value is null unless @Builder.Default is added.
Should records replace @Value? For plain immutable carriers on Java 16+, yes, records are the language-native option.
#lombok#pitfalls#jpa
Q14
What does the java.lang.Class object represent, and how can you obtain one?
basic
Class<T> is the runtime representation of a loaded type and the entry point of reflection. One Class object exists per class per classloader.
Foo.class literal (no initialization triggered).
obj.getClass() (runtime type).
Class.forName("pkg.Foo") (loads and initializes by default).
classLoader.loadClass("pkg.Foo") (does not initialize).
⚠ Follow-up traps
Does Foo.class run static initializers? No. Class.forName(name) does, loadClass does not.
Are two Class objects for the same name always equal? Not if loaded by different classloaders; this causes confusing ClassCastExceptions in app servers.
#class#reflection
Q15
How do getMethods and getDeclaredMethods differ?
basic
getMethods() returns all public methods including inherited ones (from superclasses and interfaces). getDeclaredMethods() returns all methods declared in that class only, regardless of visibility, excluding inherited ones.
The same pair exists for Field and Constructor (constructors are never inherited, so getConstructors is public only).
Order of the returned arrays is not guaranteed.
To find a private inherited method, walk getSuperclass() with getDeclaredMethods.
⚠ Follow-up traps
Does getDeclaredMethods include bridge or synthetic methods? Yes (e.g. generic bridge methods, lambdas lambda$main$0); check isBridge()/isSynthetic().
Does getMethods include Object methods? Yes, public ones such as toString, wait.
#reflection#method
Q16
How do you create an instance and call a method reflectively?
basic
Get a Constructor via getDeclaredConstructor(paramTypes) and call newInstance(args); get a Method via getMethod(name, paramTypes) and call invoke(target, args).
Class.newInstance() is deprecated since Java 9 because it propagates checked exceptions unchecked.
⚠ Follow-up traps
What exception wraps errors thrown by the target method?InvocationTargetException; use getCause().
What do you pass as target for a static method?null.
#constructor#invoke
Q17
How do you access and modify fields reflectively?
basic
Field f = cls.getDeclaredField("name"); f.setAccessible(true); f.get(obj); f.set(obj, value);. Without setAccessible(true), access to a private field throws IllegalAccessException.
getInt/setInt variants avoid boxing.
Static fields take null as the instance.
static final fields cannot be set (throws), instance final fields can be set after setAccessible(true) but the JIT may have inlined them.
⚠ Follow-up traps
Can you change a static final constant via reflection? Not in modern JDKs; also compile-time constants are inlined into callers anyway.
Can you set a final field of a record or a hidden class? No, IllegalAccessException for records and hidden classes even with setAccessible.
#field#setAccessible
Q18
What does setAccessible do and how did Java 9 modules change it?
advanced
setAccessible(true) suppresses Java access checks for that reflective object. Since Java 9, it also requires that the target package be open to the caller's module, otherwise it throws InaccessibleObjectException.
Packages are open if declared with opens pkg or opens pkg to module in module-info.java, or if the module is open module.
JDK internals: Java 9-15 default --illegal-access=permit warned; Java 16 default deny; Java 17 removed the flag. Use --add-opens java.base/java.lang=ALL-UNNAMED as workaround.
exports allows compile-time public access but not deep reflection on private members.
⚠ Follow-up traps
Does exports allow setAccessible on private fields? No, you need opens.
How do frameworks like Hibernate work on the module path? The application module must opens its entity packages to the framework module.
#setaccessible#jpms#modules
Q19
What is the cost of reflection and why is it slower?
intermediate
Reflective calls skip static linking and JIT inlining opportunities, box primitives, allocate varargs arrays, check access and wrap exceptions. Typically 2-10x slower than a direct call in a warm hot loop, but lookup (getMethod) is the larger cost.
Cache Method/Field objects; getDeclaredMethods copies arrays each call (although a root cache exists).
JDK 18+ (JEP 416) reimplemented core reflection on method handles; old native accessor "inflation" after 15 calls is gone.
Microbenchmark with JMH, not with System.nanoTime loops.
⚠ Follow-up traps
Is setAccessible(true) a speedup? It skips access checks, a small gain, but not a big one.
Is reflection always the bottleneck in Spring startup? Mostly classpath scanning and bean creation; hence AOT in Spring 6.
#reflection#performance
Q20
What are MethodHandles and how do they compare with reflection?
advanced
java.lang.invoke.MethodHandle is a typed, directly executable reference to a method, field or constructor. Access is checked once at lookup (using the caller's Lookup), and after that the JIT can inline it when the handle is a static final constant.
MethodHandles.Lookup lk = MethodHandles.lookup();MethodHandle h = lk.findVirtual(String.class, "length", MethodType.methodType(int.class));int n = (int) h.invokeExact("hello"); // 5
invokeExact needs the exact call-site signature, otherwise WrongMethodTypeException; invoke adapts.
Handles power lambdas (LambdaMetafactory), string concat and invokedynamic.
⚠ Follow-up traps
Is a MethodHandle in a non-static-final field fast? No; the JIT cannot treat it as a constant, so it behaves like a slow indirection.
Does a Lookup carry access rights? Yes, MethodHandles.lookup() has the caller's rights; use privateLookupIn to reach private members of an open module.
#methodhandle#invokedynamic#performance
Q21
What is VarHandle and why was it introduced?
advanced
VarHandle (Java 9) is a typed reference to a variable (field, array element) with fine-grained memory-order access modes (plain, opaque, acquire/release, volatile) and atomic operations like compareAndSet. It replaces most sun.misc.Unsafe usage.
Obtained with MethodHandles.lookup().findVarHandle(Cls.class, "field", int.class).
Used in ConcurrentHashMap, AtomicReference, and lock-free structures.
⚠ Follow-up traps
Is VarHandle part of reflection? It lives in java.lang.invoke, uses the same access-check model as method handles.
Does VarHandle compareAndSet work on a non-volatile field? Yes, it provides the atomicity and ordering itself.
#varhandle#java9#unsafe
Q22
What is a dynamic proxy in the JDK?
basic
java.lang.reflect.Proxy.newProxyInstance(loader, interfaces, handler) generates a class at runtime implementing the given interfaces; every call is routed to InvocationHandler.invoke(proxy, method, args).
interface Greeter { String hi(String n); }Greeter g = (Greeter) Proxy.newProxyInstance( Greeter.class.getClassLoader(), new Class<?>[]{Greeter.class}, (p, m, a) -> "proxied " + m.getName() + " " + a[0]);System.out.println(g.hi("x")); // proxied hi x
⚠ Follow-up traps
Can the JDK proxy a concrete class? No, interfaces only.
Does hashCode/equals/toString on the proxy go through the handler? Yes, those three Object methods are dispatched to the handler too.
#proxy#jdk-proxy
Q23
Compare JDK dynamic proxies and CGLIB proxies.
intermediate
JDK proxies implement interfaces and delegate to an InvocationHandler. CGLIB (and ByteBuddy) generate a subclass of the target class at runtime and override its methods, so they work without interfaces.
CGLIB cannot proxy final classes or override final/private/static methods.
Needs an accessible (or Objenesis-bypassed) constructor; Spring uses Objenesis so no default constructor is needed.
Spring Boot 2.0+ defaults to CGLIB (proxyTargetClass=true) even if interfaces exist.
Spring 6 repackages CGLIB inside spring-core; the original library is unmaintained.
⚠ Follow-up traps
Why does a final method skip the aspect? A subclass cannot override it, so calls go directly to the target logic.
Can you inject a JDK-proxied bean by its concrete class? No, the proxy is not an instance of the concrete class; this gives BeanNotOfRequiredTypeException.
#proxy#cglib#spring-aop
Q24
How do ByteBuddy, Javassist and ASM relate to proxies?
advanced
They are bytecode libraries. ASM is a low-level visitor API; Javassist offers source-like editing; ByteBuddy has a fluent, type-safe API. Hibernate (proxies, enhancement), Mockito, and Spring use them to generate classes at runtime or build time.
Java 8 lambdas themselves are spun by LambdaMetafactory/invokedynamic, not by these tools.
Java agents (-javaagent) can transform classes on load via Instrumentation.
Newer JDKs need updated ASM to read new class file versions.
⚠ Follow-up traps
Why does a new JDK sometimes break Mockito/Spring? The embedded ASM/ByteBuddy cannot parse the newer class file version until upgraded.
Does Java have a standard bytecode API? The ClassFile API is preview in 22/23 and final in 24 (JEP 484).
#bytecode#bytebuddy#asm
Q25
How does generic type information survive at runtime?
intermediate
Type erasure removes type arguments from instances (List<String> and List<Integer> share one class), but signatures of declarations are kept in the Signature attribute: field types, method parameter/return types, and superclass/interface declarations.
class Repo extends Base<User> {}Type t = Repo.class.getGenericSuperclass();Type arg = ((ParameterizedType) t).getActualTypeArguments()[0];System.out.println(arg); // class User
Field.getGenericType(), Method.getGenericReturnType() give ParameterizedType.
Local variables and new ArrayList<String>() instance type arguments are not retrievable.
⚠ Follow-up traps
Can I find T from new ArrayList<String>().getClass()? No, it is ArrayList with TypeVariable E.
Why do lambdas lose the generic type? Lambda classes have no generic superclass signature.
#generics#type-erasure#reflection
Q26
What is the super type token pattern (TypeToken, TypeReference)?
intermediate
Create an anonymous subclass of a generic class so the actual type argument is recorded in its superclass signature, then read it with getGenericSuperclass(). Gson's TypeToken, Jackson's TypeReference and Spring's ParameterizedTypeReference use this trick.
abstract class Token<T> { final Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];}Type t = new Token<List<String>>() {}.type;System.out.println(t); // java.util.List<java.lang.String>
The braces {} are essential; without them the subclass does not exist.
⚠ Follow-up traps
What if you forget {}? There is no anonymous subclass, so you get a raw Token and ClassCastException (the generic superclass is Object).
Why can't I do objectMapper.readValue(json, List<User>.class)?List<User>.class is illegal; without a TypeReference you get List<LinkedHashMap>.
#typetoken#generics#jackson
Q27
How does Spring use reflection and annotations to build the context?
intermediate
Spring scans the classpath for classes carrying stereotype annotations (@Component and its meta-annotated children), but reads class files with ASM (MetadataReader) rather than loading them. It then registers BeanDefinitions, instantiates beans through constructors (reflection), and injects dependencies into fields, setters and constructor parameters.
@Service, @Repository, @Controller are meta-annotated with @Component.
@Configuration classes are CGLIB-subclassed so @Bean methods return singletons.
⚠ Follow-up traps
Does component scanning load every class? No, ASM metadata reading avoids loading and initializing them.
Why is @Configuration proxied but @Component with @Bean methods ("lite mode") not? Lite mode does not intercept inter-bean method calls, so @Bean calls create new instances.
#spring#component-scan#reflection
Q28
What are merged or composed annotations in Spring?
intermediate
Spring treats an annotation placed on another annotation as a meta-annotation and can synthesize attribute overrides with @AliasFor. This lets you build custom stereotypes like @RestController = @Controller + @ResponseBody.
JDK reflection cannot read @Service through @ReadService; Spring's MergedAnnotations can.
⚠ Follow-up traps
Does clazz.isAnnotationPresent(Service.class) return true for @ReadService? No, only Spring's utilities see meta-annotations.
Can @AliasFor link attributes within one annotation? Yes, e.g. value and path in @RequestMapping.
#spring#meta-annotation#composed
Q29
How do @Transactional and other Spring AOP annotations work?
intermediate
A BeanPostProcessor (AbstractAutoProxyCreator) wraps beans that match an advisor into a JDK or CGLIB proxy. The proxy's interceptor (TransactionInterceptor) reads @Transactional via reflection on the target method, begins a transaction, calls the target, then commits or rolls back.
Only calls going through the proxy are advised; this.method() self-invocation bypasses it.
By default only public methods (for proxy-based AOP) and RuntimeException/Error trigger rollback.
AspectJ weaving avoids the proxy limits.
⚠ Follow-up traps
Why does @Transactional on a private method do nothing? Proxies cannot intercept it, and Spring ignores non-public methods in proxy mode.
Does a checked exception roll back? Not by default; use rollbackFor.
#spring#aop#proxy#transactional
Q30
How does Hibernate/JPA use annotations and reflection?
intermediate
Hibernate reads @Entity, @Id, @Column, associations etc. to build mapping metadata. It instantiates entities through a no-arg constructor (any visibility, via reflection) and reads/writes state through fields or getters depending on where @Id is placed (access type).
Lazy @ManyToOne/getReference return a ByteBuddy subclass proxy of the entity.
This is why entities should be non-final with a no-arg constructor (at least protected).
Bytecode enhancement can add dirty tracking and lazy attributes at build time.
⚠ Follow-up traps
Why can't a JPA entity be a final class? Hibernate cannot create a lazy-loading subclass proxy.
Can a Java record be an @Entity? No, entities need mutable state and a no-arg constructor; records can be used as projections or embeddables (Hibernate 6.2+ for embeddables).
#hibernate#jpa#entity#proxy
Q31
How does JUnit use annotations and reflection?
basic
JUnit 5 discovers classes and methods annotated with @Test, @ParameterizedTest, @BeforeEach etc. through reflection, creates a test-class instance (by default one per test method), and invokes the methods, resolving parameters through ParameterResolver extensions.
Test methods and classes can be package-private; JUnit calls setAccessible.
@ExtendWith registers extensions; @Tag, @Disabled and @Nested shape discovery.
Composed annotations are supported (meta-annotations).
⚠ Follow-up traps
Why does JUnit 5 create a new instance per test? Isolation; change with @TestInstance(Lifecycle.PER_CLASS).
Can @BeforeAll methods be non-static? Only with PER_CLASS lifecycle.
#junit#testing
Q32
How does Spring's @Autowired field injection actually happen?
intermediate
After instantiation, AutowiredAnnotationBeanPostProcessor inspects the class (and superclasses) for @Autowired fields and methods, resolves each dependency from the container, then calls field.setAccessible(true) and field.set(bean, dep).
No constructor or setter participates, so the object can exist in an invalid state when created outside Spring.
Fields cannot be final.
Constructor injection is preferred: immutability, explicit dependencies, easy plain-Java tests.
⚠ Follow-up traps
Is the injection order field-then-constructor? Constructor first (bean creation), then field/setter injection.
Does @Autowired on a field work in a class created with new? No, only for container-managed beans.
#spring#di#field-injection
Q33
What does Class.forName do and how does it differ from ClassLoader.loadClass?
intermediate
Class.forName(name) loads, links and initializes the class using the caller's classloader, running static initializers. ClassLoader.loadClass(name) only loads it; initialization is deferred until first active use.
Class.forName(name, false, loader) loads without initialization.
Old JDBC code used Class.forName("com.mysql.Driver") to trigger driver registration in a static block; with ServiceLoader (JDBC 4) it is no longer needed.
⚠ Follow-up traps
Which classloader does Class.forName use? The defining loader of the calling class, which matters in web containers.
What does Thread.getContextClassLoader() solve? It lets framework (parent-loaded) code load app classes from the child loader.
#classloader#initialization
Q34
What is ServiceLoader and how does it relate to reflection and annotations?
intermediate
ServiceLoader discovers implementations of an interface listed in META-INF/services/<fqcn> (or provides ... with in module-info) and instantiates them reflectively. It is the standard plugin mechanism and the usual target output of an annotation processor like AutoService.
Instantiation is lazy and iterates providers.
Providers need a public no-arg constructor or a public static provider() method (modules).
⚠ Follow-up traps
Is ServiceLoader thread-safe? No, it is not; do not share an instance between threads.
Who generates the META-INF/services file with @AutoService? An annotation processor at compile time.
#serviceloader#spi
Q35
How do records and enums interact with reflection?
intermediate
Records expose Class.isRecord() and getRecordComponents() giving name, type, generic type, accessor and annotations of each component. Enum constants are available via getEnumConstants(), and enums cannot be instantiated reflectively (newInstance throws IllegalArgumentException).
Record fields are private final; reflection Field.set fails even with setAccessible.
Annotations on a record component propagate to field, accessor, constructor parameter and/or record component depending on the annotation's @Target.
⚠ Follow-up traps
Can you create an enum instance by reflection? No, Cannot reflectively create enum objects.
Does @NotNull on a record component validate the field? Only if its @Target includes FIELD/METHOD etc. as the framework needs; Bean Validation's targets cover them.
#records#enums#reflection
Q36
How does serialization relate to reflection, and what are the security concerns?
advanced
Java deserialization (ObjectInputStream) reconstructs objects and invokes readObject hooks reflectively without calling constructors of serializable classes. Gadget chains in libraries can reach Method.invoke and give remote code execution.
Avoid native serialization of untrusted input; use ObjectInputFilter (JEP 290, JEP 415 context filters) with allow-lists.
Jackson default typing (enableDefaultTyping) has similar polymorphic gadget risks; use PolymorphicTypeValidator.
Prefer JSON/Protobuf with explicit schemas.
⚠ Follow-up traps
Does a transient field guard from deserialization attacks? No, it just skips that field; the hooks still run.
Is it safe to use Class.forName(userInput)? No, it lets attackers trigger static initializers of arbitrary classes.
#security#serialization#reflection
Q37
What security controls exist around reflection?
advanced
Historically the SecurityManager checked ReflectPermission("suppressAccessChecks") before setAccessible. The SecurityManager is deprecated for removal (Java 17) and effectively disabled in Java 24 (JEP 486). Today, the real controls are module encapsulation (opens), strong encapsulation of JDK internals, and review of reflective sinks.
Treat any Class.forName, Method.invoke or getMethod driven by user input as a vulnerability (reflection injection).
Use allow-lists mapping names to handlers instead of reflective dispatch.
Sealed classes and final prevent unexpected subclassing but not reflection.
⚠ Follow-up traps
Can private be relied on as a security boundary? No, within a single module/classpath reflection bypasses it.
Does strong encapsulation stop code on the classpath? The classpath is the unnamed module; it can reflect into other unnamed code but not into closed JDK packages.
#security#securitymanager#jpms
Q38
What is Unsafe and what replaced it?
advanced
sun.misc.Unsafe gives raw memory access, CAS and uninitialized object allocation. It is not an official API and is being phased out (JEP 471/498 deprecate its memory-access methods for removal).
VarHandle replaces field CAS and ordered access.
MemorySegment (Foreign Function & Memory API, final in Java 22) replaces off-heap access.
Serialization libraries used Unsafe.allocateInstance to skip constructors; Spring/Objenesis do this too.
⚠ Follow-up traps
Is Unsafe blocked by the module system?sun.misc is exported by jdk.unsupported, so it remains reachable, with warnings on newer releases.
Can you instantiate a class without any constructor call? Yes through Unsafe/Objenesis or deserialization tricks, but invariants are bypassed.
#unsafe#varhandle#ffm
Q39
How does GraalVM native image handle reflection?
advanced
Native image performs closed-world static analysis at build time; code reachable only through reflection is invisible and removed unless declared. You must supply configuration listing classes, methods and fields that need reflective access.
Config lives in META-INF/native-image/<group>/<artifact>/reachability-metadata.json (older: reflect-config.json, proxy-config.json, resource-config.json, serialization-config.json, jni-config.json).
Generate with the tracing agent: java -agentlib:native-image-agent=config-output-dir=... -jar app.jar.
Spring uses @RegisterReflectionForBinding, @ImportRuntimeHints and AOT processing; Quarkus uses @RegisterForReflection.
The GraalVM Reachability Metadata Repository supplies configs for common libraries.
⚠ Follow-up traps
Why does Jackson fail only in native mode? Reflective access to DTO constructors/getters was not registered, so serialization misses or throws.
Can dynamic classloading and Proxy work? Proxies need declared interface lists at build time; arbitrary runtime class loading and bytecode generation (CGLIB) are unsupported.
#graalvm#native-image#aot
Q40
How does Spring AOT reduce reflection?
advanced
Spring 6 / Boot 3 can run an AOT phase at build time that evaluates conditions and generates Java source for bean definitions and registration code, plus runtime hints for the remaining reflection, proxies and resources. This enables native image and faster JVM startup.
Profiles and @Conditional outcomes are fixed at build time.
Hints come from RuntimeHintsRegistrar, @RegisterReflectionForBinding, and built-in contributors.
Proxies of configuration classes are generated at build time, not at runtime.
⚠ Follow-up traps
Can I change active profiles at native-image runtime? Bean conditions were already decided during AOT; changing profiles later does not re-evaluate them.
Is reflection gone entirely? No, constructor and field access for remaining cases still goes through hints.
#spring#aot#native-image
Q41
What are the differences between getAnnotation on a Class and on a Method for inheritance?
intermediate
Class-level lookup honours @Inherited through superclasses. Method-level lookup never inherits: if a subclass overrides save(), getMethod("save").getAnnotation(...) shows only annotations declared on the overriding method.
Why does my @Transactional on the interface method sometimes work and sometimes not? Depends on proxy type and Spring version; CGLIB proxies with class-based lookup historically ignored interface annotations, now Spring searches interfaces too.
Are annotations on an abstract class's method seen on a concrete override? Not by plain reflection.
#annotations#inheritance#reflection
Q42
What are bridge and synthetic methods, and why do they matter for reflection?
advanced
For generics and covariant returns, the compiler creates synthetic bridge methods with the erased signature that delegate to the real method. Reflection (getDeclaredMethods) shows both.
class Box implements Comparable<Box> { public int compareTo(Box o) { return 0; } // compiler adds: public int compareTo(Object o) { return compareTo((Box) o); }}
Frameworks scanning annotated methods must skip bridges (Method.isBridge()) or they find duplicates.
Annotations were not copied to bridge methods before Java 8 (javac 8 copies them), a known source of bugs.
⚠ Follow-up traps
Why would a scanner call a handler twice? It matched both the real method and its bridge.
Is a lambda body a synthetic method? Yes, lambda$name$n is private static synthetic.
#bridge#synthetic#generics
Q43
How do parameter names work with reflection?
intermediate
By default javac does not retain parameter names, so Parameter.getName() returns arg0, arg1. Compile with -parameters to store them (Parameter.isNamePresent() becomes true). Spring Boot's Maven/Gradle plugins enable this flag by default.
Debug info (-g) stores local variable names, which Spring's LocalVariableTableParameterNameDiscoverer used; removed in Spring 6.1.
Record components always keep names.
⚠ Follow-up traps
Why does @RequestParam without a name break after upgrading to Spring 6.1? Names now require -parameters; the debug-info fallback is gone.
Do @PathVariable names work with plain javac? Not unless -parameters or an explicit name is given.
#parameters#compiler-flag
Q44
What is the difference between Class.getName, getSimpleName, getCanonicalName and getTypeName?
basic
For a nested class Outer.Inner in package p: getName() is p.Outer$Inner, getSimpleName() is Inner, getCanonicalName() is p.Outer.Inner, getTypeName() is like getName except arrays print as p.Outer$Inner[].
Arrays: int[].class.getName() is [I, String[].class.getName() is [Ljava.lang.String;.
Anonymous classes: simple name is empty, canonical name is null.
⚠ Follow-up traps
Which name does Class.forName accept? The binary name (p.Outer$Inner), not the canonical one.
What does getCanonicalName return for a lambda or anonymous class?null.
#class#naming
Q45
How do you detect and call a default or private interface method reflectively?
advanced
A default method is found through getMethod and invoked normally on an implementing instance. Invoking the default implementation from within a JDK proxy handler needs InvocationHandler.invokeDefault(proxy, method, args) (Java 16+). Before, you needed MethodHandles.privateLookupIn hacks.
InvocationHandler h = (p, m, a) -> m.isDefault() ? InvocationHandler.invokeDefault(p, m, a) : null;
This is how Spring Data repository proxies and Feign support default methods.
⚠ Follow-up traps
Why does method.invoke(proxy, args) inside the handler loop forever? It re-enters the proxy and the handler again.
Can invokeDefault call an abstract method? No, it throws IllegalArgumentException.
#default-methods#methodhandle#proxy
Q46
What is the difference between annotation processing and reflection at runtime, and when to choose each?
intermediate
Annotation processing generates code at compile time (zero reflection cost, errors reported at build, native-image friendly) but needs build tooling and cannot see runtime state. Reflection is flexible and needs no build step but is slower, bypassed by obfuscation, and hostile to AOT.
Spring historically chose runtime reflection; Spring 6 AOT narrows the gap.
Choose generation when startup, native image or compile-time safety matters.
⚠ Follow-up traps
Can a processor validate that a method has the right signature? Yes, and fail the build with Messager errors.
Does build-time generation remove the need for -parameters or opens? Largely yes, since generated code calls members directly.
#apt#reflection#trade-offs
Q47
What is an annotation's generated proxy and how do equals, hashCode and toString behave on it?
advanced
AnnotationParser creates a JDK proxy with an AnnotationInvocationHandler holding a map of member values. equals compares member values with another annotation of the same type, hashCode is the sum of (127 * name.hashCode()) ^ valueHash, and toString renders @pkg.Timed(name="", warnAboveMs=500).
Calling a member method is a map lookup; array members return a clone every call.
Instances are cached per element, so repeated getAnnotation is cheap after the first parse.
⚠ Follow-up traps
Is it safe to mutate the array returned by an annotation member? Yes, you get a clone, the annotation is not changed.
Can I implement an annotation interface with my own class? Technically yes (used to create annotation literals in CDI), but it should honour the equals/hashCode contract.
#annotations#proxy#internals
Q48
How does Bean Validation (Jakarta Validation) use annotations?
intermediate
Constraint annotations like @NotNull, @Size, @Email are meta-annotated with @Constraint(validatedBy = X.class). The validator (Hibernate Validator) reads them reflectively from fields, getters, parameters and type-use positions, instantiates the ConstraintValidator<A, T> and calls isValid.
Required members: message, groups, payload.
Cascading uses @Valid.
Spring triggers it via @Valid/@Validated on controller arguments or via a method-validation AOP proxy.
⚠ Follow-up traps
Is null valid for @Size? Yes, null is considered valid; combine with @NotNull.
Why does @Valid on a nested field not cascade without the annotation? Cascading is opt-in per property.
#validation#constraint#jakarta
Q49
How do you load and inspect classes without initializing them, for example to scan a package?
advanced
The JDK has no package-listing API; libraries (Spring ClassPathScanningCandidateComponentProvider, ClassGraph, Reflections) list .class resources from directories and jars and read annotations by parsing bytecode (ASM) so classes are not loaded or initialized.
Loading everything with Class.forName triggers static initializers, class loading memory, and possibly NoClassDefFoundError.
Scanning is a major startup cost; narrow base packages.
⚠ Follow-up traps
Why not ClassLoader.getResources("com/foo") and list files? It fails inside jars and layered or nested jar formats without extra handling.
Does the module path offer a scan?ModuleLayer/ModuleReference.open().list() lists resources of a module.
#classpath-scanning#asm#classgraph
Q50
What are hidden classes and how do they affect reflection?
advanced
Hidden classes (Java 15, JEP 371) are classes defined with Lookup.defineHiddenClass that cannot be linked to by name from other classes and are unloadable independently. Lambdas and invokedynamic infrastructure use them.
Class.forName cannot find them; getName has a /0x... suffix.
Their fields cannot be modified through Field.set even after setAccessible.
Frameworks should use them instead of ClassLoader.defineClass or Unsafe.defineAnonymousClass (removed in 17).
⚠ Follow-up traps
Is a lambda's class named like Foo$$Lambda$14? In modern JDKs it is a hidden class Foo$$Lambda/0x....
Can a JDK proxy's class be hidden? JDK 15+ may define proxies for non-exported interfaces in dynamic modules, but the class is still normal, not hidden.
#hidden-classes#lookup#java15
Q51
What does Method.invoke do with varargs, boxing and exceptions?
intermediate
invoke(Object target, Object... args) boxes primitive arguments, unboxes primitive returns to wrappers, and widens primitives automatically. Exceptions thrown by the target are wrapped in InvocationTargetException; wrong arg count or type gives IllegalArgumentException.
For a target method that itself takes Object... or an array, pass new Object[]{ new String[]{"a"} } to avoid ambiguity.
A null target for an instance method gives NullPointerException.
Unwrap with e.getCause() and rethrow, or the real error is hidden.
⚠ Follow-up traps
m.invoke(obj, new String[]{"a","b"}) for foo(String...): what happens? The array spreads as two arguments, causing IllegalArgumentException: wrong number of arguments; wrap it in new Object[]{array}.
Does invoke honour polymorphism? Yes, virtual dispatch on the target instance, like a normal call.
#invoke#varargs#exceptions
Q52
How do annotations interact with inheritance in interfaces vs. proxies when using Spring Data or Feign?
intermediate
Declarative clients (Spring Data repositories, Feign, Retrofit, MyBatis mappers) are interfaces with no implementation; the framework creates a JDK proxy and its handler reads annotations from the interface methods (@Query, @GetMapping, @Select) to decide behaviour.
Method name parsing (findByEmailAndStatus) in Spring Data is also reflection on method names.
Each interface method's metadata is parsed once at startup and cached.
⚠ Follow-up traps
Why is no implementation class needed? The proxy's handler is the implementation.
What happens with a typo in a derived query name? Startup fails with PropertyReferenceException, not at first call.
#proxy#spring-data#interface-annotations
Scenarios
Q53
Annotation missing at runtime: what is the output?
basic
Expected output is null, because the default retention is CLASS.
Fix by adding @Retention(RetentionPolicy.RUNTIME).
⚠ Follow-up traps
Does the annotation exist in the .class file? Yes, as RuntimeInvisibleAnnotations, visible to ASM/agents but not reflection.
Would isAnnotationPresent behave differently? No, it also returns false.
#retention#debugging
Q54
Build an @Timed logging aspect with a JDK dynamic proxy.
intermediate
Wrap the target in a proxy; the handler checks the annotation on the target's method (the interface Method passed to the handler does not carry implementation annotations).
@Retention(RetentionPolicy.RUNTIME) @interface Timed {}interface Svc { void work(); }class SvcImpl implements Svc { @Timed public void work() {} }static <T> T timed(T target, Class<T> iface) { return iface.cast(Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[]{iface}, (p, m, a) -> { Method impl = target.getClass().getMethod(m.getName(), m.getParameterTypes()); if (!impl.isAnnotationPresent(Timed.class)) return m.invoke(target, a); long t = System.nanoTime(); try { return m.invoke(target, a); } catch (InvocationTargetException e) { throw e.getCause(); } finally { System.out.println(m.getName() + " " + (System.nanoTime() - t)); } }));}
⚠ Follow-up traps
Why look at target.getClass() and not m?m is the interface method; the annotation sits on the implementation.
Why unwrap InvocationTargetException? Otherwise callers get an UndeclaredThrowableException for checked exceptions.
#proxy#annotations#aop
Q55
Write a custom validation annotation @NotBlankIfActive using Jakarta Validation.
intermediate
Define the annotation with @Constraint, then implement ConstraintValidator. For cross-field rules, annotate the class and read two properties.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)@interface Min { int value(); }static List<String> validate(Object o) throws IllegalAccessException { List<String> errs = new ArrayList<>(); for (Field f : o.getClass().getDeclaredFields()) { Min m = f.getAnnotation(Min.class); if (m == null) continue; f.setAccessible(true); if (((Number) f.get(o)).intValue() < m.value()) errs.add(f.getName() + " must be >= " + m.value()); } return errs;}// class P { @Min(18) int age = 10; } -> [age must be >= 18]
⚠ Follow-up traps
Does this see superclass fields? No, getDeclaredFields is per class; walk getSuperclass().
What if the field is nullInteger?NullPointerException on the cast/unboxing; handle null explicitly.
#validation#reflection#custom
Q57
Build a mini dependency-injection container with constructor injection.
advanced
Register classes, pick the single constructor, resolve each parameter recursively, cache singletons, and detect cycles.
public class MiniContainer { private final Map<Class<?>, Object> singletons = new HashMap<>(); private final Set<Class<?>> creating = new HashSet<>(); @SuppressWarnings("unchecked") public <T> T get(Class<T> type) throws Exception { Object existing = singletons.get(type); if (existing != null) return (T) existing; if (!creating.add(type)) throw new IllegalStateException("Cycle: " + type.getName()); try { Constructor<?> c = type.getDeclaredConstructors()[0]; Object[] args = new Object[c.getParameterCount()]; Class<?>[] pt = c.getParameterTypes(); for (int i = 0; i < args.length; i++) args[i] = get(pt[i]); c.setAccessible(true); Object o = c.newInstance(args); singletons.put(type, o); return (T) o; } finally { creating.remove(type); } }}
Real containers add interface-to-implementation bindings, scopes, qualifiers, and @Inject-based constructor selection.
⚠ Follow-up traps
How does constructor injection detect circular dependencies? The creating set; Spring also fails for constructor cycles since Boot 2.6 even for setter cycles unless allowed.
Why is getDeclaredConstructors()[0] fragile? Order is unspecified; pick the annotated one or the only one explicitly.
Is this container thread-safe? No; use ConcurrentHashMap.computeIfAbsent with care (recursive modification) or a lock.
#di#container#design
Q58
Extend the mini container to support an @Inject annotation on fields and an interface binding.
advanced
Add a bind(Class<I>, Class<? extends I>) map and, after construction, scan fields for @Inject, resolve the field's type through the binding map, and set it.
private final Map<Class<?>, Class<?>> bindings = new HashMap<>();public <I> void bind(Class<I> i, Class<? extends I> impl) { bindings.put(i, impl); }private void injectFields(Object o) throws Exception { for (Class<?> c = o.getClass(); c != Object.class; c = c.getSuperclass()) for (Field f : c.getDeclaredFields()) if (f.isAnnotationPresent(Inject.class)) { f.setAccessible(true); f.set(o, get(bindings.getOrDefault(f.getType(), f.getType()))); }}
Walking superclasses matters; @Inject in base classes would otherwise be skipped.
⚠ Follow-up traps
What if an interface has no binding?get(interface) fails at getDeclaredConstructors()[0] (interfaces have none); throw a clear "no binding" error.
What breaks with a final injected field?Field.set on a final instance field works after setAccessible, but it is hostile and may be constant-folded; do not rely on it.
#di#field-injection#binding
Q59
Why does field injection make a class hard to unit test, and what happens?
intermediate
With @Autowired private Repo repo;, new Service() yields an object whose repo is null; the first call throws NullPointerException. There is no constructor or setter to supply a mock, so tests need the Spring context, @InjectMocks (Mockito reflection) or ReflectionTestUtils.setField.
class Service { @Autowired private Repo repo; String name(int id) { return repo.find(id).name(); } }new Service().name(1); // NullPointerException
Constructor injection makes the dependency explicit, allows final fields, and tests use plain new Service(mockRepo).
⚠ Follow-up traps
Does @InjectMocks always inject? It tries constructor, then property setter, then field injection by type; if two fields share a type it falls back by name, and failures are silent.
Does @Mock initialize without MockitoExtension/openMocks? No, mocks stay null.
#field-injection#testing#spring
Q60
A Spring test with @Autowired field in a plain JUnit class fails with NPE. Why?
basic
Plain JUnit does not process Spring annotations; no container exists, so @Autowired is just metadata nobody reads. Fix with @SpringBootTest/@ExtendWith(SpringExtension.class), or avoid Spring with a constructor-injected unit test.
@SpringBootTest loads the full context (slow); slices (@WebMvcTest, @DataJpaTest) load less.
JUnit 5 + Spring: the SpringExtension acts as the test instance post-processor.
⚠ Follow-up traps
Does @Autowired work in a test class constructor? Yes with JUnit 5 SpringExtension as a ParameterResolver.
Does @MockBean work without Spring? No; with Mockito only, use @Mock.
#spring#junit#testing
Q61
Reflection throws InaccessibleObjectException on JDK 17. What now?
advanced
Message: Unable to make field private final ... accessible: module java.base does not "opens java.lang" to unnamed module. JDK 17 strongly encapsulates internals.
Options in order of preference:
Upgrade the library to a version that uses public APIs, MethodHandles.privateLookupIn or VarHandle.
Use --add-opens java.base/java.lang=ALL-UNNAMED (JVM flag, JAVA_TOOL_OPTIONS, or in the jar manifest Add-Opens:).
For your own modules, add opens com.app.model to jackson.databind;.
⚠ Follow-up traps
Is --illegal-access=permit still available on 17? No, the flag was removed; the JVM warns that it is ignored.
Does --add-exports work for reflection? No; it makes public types accessible, --add-opens is needed for deep reflection.
#jpms#add-opens#migration
Q62
What is printed and why: reflection on a private constructor of a singleton?
intermediate
Reflection can break a class-based singleton by calling its private constructor.
class Single { static final Single I = new Single(); private Single() {} }Constructor<Single> c = Single.class.getDeclaredConstructor();c.setAccessible(true);System.out.println(c.newInstance() == Single.I); // false
Mitigations: throw from the constructor if I != null, or use an enum singleton, which Constructor.newInstance explicitly refuses.
⚠ Follow-up traps
Does enum singleton survive serialization attacks too? Yes, enum deserialization returns the existing constant.
Does a module boundary stop this? Only when the package is not open to the caller.
#singleton#reflection#enum
Q63
Why does this CGLIB-proxied bean throw NullPointerException on a field but not on a method?
advanced
The CGLIB proxy is a subclass instance whose own fields are uninitialized (created via Objenesis, no constructor run). Method calls are intercepted and delegated to the real target, but direct field access on the proxy (e.g. other.name from the same package or a final method call) reads the proxy's empty fields.
@Service class Price { String currency = "USD"; public final String cur() { return currency; } }// injected proxy: proxy.currency == null; proxy.cur() -> final, not intercepted, runs on proxy -> null
Use getters (non-final), keep logic in non-final public methods, avoid exposing fields.
⚠ Follow-up traps
Why is the final method returning null? It is not overridden by the subclass, so it executes against the proxy instance's uninitialized state.
Why does Kotlin hit this often? Classes and members are final by default; use the kotlin-spring all-open plugin.
#cglib#proxy#spring
Q64
@Transactional method calls another @Transactional(REQUIRES_NEW) method in the same class. What happens?
intermediate
The inner call does not start a new transaction; it runs inside the outer one. Self-invocation uses this, not the proxy, so the advice never runs.
Fixes: move audit into another bean, inject self lazily, use TransactionTemplate, or AspectJ weaving mode.
⚠ Follow-up traps
Would making audit public help? Not for self-invocation; the proxy is still bypassed.
If audit throws, is the outer rolled back? The exception propagates through the outer proxy and triggers rollback of the shared transaction.
#spring#self-invocation#transactional
Q65
A custom annotation on an interface method is not found on the implementation. What to do?
intermediate
method.getAnnotation only sees annotations declared directly on that Method. For overridden methods, search the hierarchy yourself or use Spring's AnnotatedElementUtils.findMergedAnnotation(method, Type.class), which looks at superclass and interface methods.
Audit a = AnnotatedElementUtils.findMergedAnnotation(impl, Audit.class);
Alternatively put the annotation on the class and make it @Inherited.
⚠ Follow-up traps
Does @Inherited fix the method case? No, it only applies to class annotations.
Which method do you pass in a proxy handler? Use AopUtils.getMostSpecificMethod(method, targetClass).
#annotations#inheritance#spring
Q66
Jackson deserializes List<User> as List<LinkedHashMap>. Why and how to fix?
basic
readValue(json, List.class) supplies only the raw type, so elements default to LinkedHashMap. Pass full generic type information with TypeReference.
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});// ormapper.getTypeFactory().constructCollectionType(List.class, User.class);
The failure shows later as ClassCastException: LinkedHashMap cannot be cast to User at use.
⚠ Follow-up traps
Why does the error appear far from readValue? Erasure removes casts until element access inserts a checkcast.
Does new TypeReference<List<User>>() without {} compile? No, TypeReference is abstract.
#jackson#typetoken#erasure
Q67
How would you find the actual type argument T in `abstract class Repo<T>` for `class UserRepo extends Repo<User>`?
intermediate
Read the generic superclass of the concrete class.
abstract class Repo<T> { @SuppressWarnings("unchecked") final Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];}class UserRepo extends Repo<User> {}System.out.println(new UserRepo().type); // class User
Breaks for new Repo<User>() {}? No, that works too; but class Mid<T> extends Repo<T> and Mid<User> instances yield TypeVariable and ClassCastException. Spring's GenericTypeResolver.resolveTypeArgument handles hierarchies.
⚠ Follow-up traps
What does it return for new Repo<User>() {}? Works, the anonymous subclass records User.
What fails with an intermediate generic subclass? The type argument is a TypeVariable; walk the hierarchy.
#generics#reflection
Q68
A JUnit test needs to call a private method. Should you use reflection?
basic
Prefer testing through the public API. If the private logic is complex enough to need its own tests, extract it into a package-private or separate class. Reflection (setAccessible) works but couples tests to implementation names and breaks silently under refactoring.
Method m = Calc.class.getDeclaredMethod("round", double.class);m.setAccessible(true);double r = (double) m.invoke(new Calc(), 2.567);
⚠ Follow-up traps
Does it work on a modularized JDK class? Only if the package is opened to the test; for JDK internals it fails on 17+.
Does ReflectionTestUtils.invokeMethod differ? It is Spring's wrapper, same mechanism with friendlier errors.
#testing#private#design
Q69
A hot loop calls Method.invoke 10 million times and is slow. How do you speed it up?
advanced
First, hoist getMethod and setAccessible out of the loop. Then, for the remaining overhead, use a static final MethodHandle (inlineable) or generate a lambda with LambdaMetafactory for a typed functional interface. Best of all, avoid reflection by generating code at build time.
private static final MethodHandle LEN;static { try { LEN = MethodHandles.lookup().findVirtual( String.class, "length", MethodType.methodType(int.class));} catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } }int n = (int) LEN.invokeExact("abc");
⚠ Follow-up traps
Why must the handle be static final? The JIT trusts only constants for inlining.
Why benchmark with JMH? Dead-code elimination and warm-up distort naive timers.
#performance#methodhandle#caching
Q70
What happens when a reflected method throws a checked exception?
basic
The caller receives InvocationTargetException whose getCause() is the original exception. The stack trace shows reflection frames.
try { m.invoke(obj); }catch (InvocationTargetException e) { Throwable real = e.getCause(); // e.g. IOException throw real; // rethrow original}
With a JDK proxy, a checked exception thrown from the handler that the interface does not declare becomes UndeclaredThrowableException.
⚠ Follow-up traps
Is InvocationTargetException thrown for Errors? Yes, errors are wrapped too.
What is thrown if the method was inaccessible?IllegalAccessException, thrown directly, not wrapped.
#exceptions#invoke
Q71
A class fails to cast to itself: ClassCastException "com.A cannot be cast to com.A". Why?
advanced
Two different classloaders loaded com.A; a class's identity is (name, defining loader). Typical in app servers, OSGi, Spring Boot devtools restart loader, or plugin systems.
Fix by ensuring the type comes from a shared parent loader (e.g. put the API in the server's lib), or communicate through interfaces loaded by a common parent.
Diagnose with a.getClass().getClassLoader() for both objects.
⚠ Follow-up traps
Does devtools restart cause this? Yes, objects cached across a restart (static caches in libraries loaded by the base loader) hold classes from the old restart loader.
Can serialization fix it? Serialize to bytes and deserialize with the target loader, a common workaround.
#classloader#classcastexception
Q72
Static initializer runs unexpectedly with Class.forName. What is the output?
basic
Output order demonstrates that forName initializes while .class and loadClass do not.
class Boot { static { System.out.println("init"); } }Class<?> a = Boot.class; // nothingClass<?> b = Boot.class.getClassLoader().loadClass("Boot"); // nothingClass<?> c = Class.forName("Boot"); // prints: init
Initialization is triggered once, on first new, static method call, non-constant static field access, or forName.
⚠ Follow-up traps
Does reading a static final int X = 5 constant initialize the class? No, it is inlined at compile time.
Does it run twice for forName called twice? No, only once per classloader.
#class-loading#initialization
Q73
A GraalVM native image works with Jackson in tests but fails in production with empty JSON. What happened?
advanced
The DTOs were not registered for reflection, so Jackson found no accessible properties (or no constructor) in the native binary. On the JVM, reflection works without registration.
Fix:
@RegisterReflectionForBinding({OrderDto.class, ItemDto.class})@Configuration class Hints {}
Or provide reflect-config.json, or run the tracing agent in tests (-agentlib:native-image-agent).
Spring detects types in controller signatures automatically, but not types used only through ObjectMapper.convertValue.
⚠ Follow-up traps
Why did it pass JVM tests? The JVM has no closed-world restriction; run nativeTest to catch it.
Do records need registration? Yes, their accessors and canonical constructors need hints too.
#graalvm#jackson#native-image
Q74
Fix a native-image failure caused by a JDK dynamic proxy.
advanced
Native image needs to know proxy interface sets at build time. The error is Proxy class defined by interfaces [...] not found. Register the exact ordered interface list in proxy configuration.
(Older layout: proxy-config.json with [["com.app.PaymentClient"]].)
Order of interfaces matters and each distinct combination is its own entry.
Spring AOT usually generates these hints; add @ImportRuntimeHints for custom proxies.
⚠ Follow-up traps
Can CGLIB run at native image runtime? No, runtime bytecode generation is unsupported; Spring generates the subclasses at build time.
Does the order of interfaces matter? Yes, it defines the proxy class.
#graalvm#proxy
Q75
Design a custom @RateLimited annotation processed by Spring AOP. What are the considerations?
advanced
Define a runtime, method-target annotation and an @Aspect with @Around("@annotation(rl)") that binds the annotation instance.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)public @interface RateLimited { int perMinute() default 60; }@Aspect @Componentclass RateLimitAspect { @Around("@annotation(rl)") Object limit(ProceedingJoinPoint pjp, RateLimited rl) throws Throwable { if (!bucketFor(pjp).tryAcquire(rl.perMinute())) throw new TooManyRequestsException(); return pjp.proceed(); }}
Key by user/method; use Redis or Bucket4j for multi-instance limiting.
Remember self-invocation, final methods and order relative to @Transactional (@Order).
⚠ Follow-up traps
Why use @annotation(rl) with a parameter rather than getMethod().getAnnotation? Spring resolves the merged annotation from the real target method, avoiding the interface-vs-impl lookup bug.
Is local in-memory limiting correct with 3 instances? No, effective limit becomes 3x.
#spring-aop#custom-annotation#design
Q76
Write an annotation processor that fails the build when a @Builder-like class has no no-arg constructor.
advanced
Extend AbstractProcessor, iterate annotated elements, inspect enclosed constructors with javax.lang.model, and report with Messager.
@SupportedAnnotationTypes("com.app.NeedsNoArg")@SupportedSourceVersion(SourceVersion.RELEASE_17)public class NoArgCheck extends AbstractProcessor { public boolean process(Set<? extends TypeElement> ann, RoundEnvironment env) { for (Element e : env.getElementsAnnotatedWith(NeedsNoArg.class)) { boolean ok = ElementFilter.constructorsIn(e.getEnclosedElements()) .stream().anyMatch(c -> c.getParameters().isEmpty()); if (!ok) processingEnv.getMessager() .printMessage(Diagnostic.Kind.ERROR, "needs no-arg ctor", e); } return false; }}
Register via META-INF/services/javax.annotation.processing.Processor (or @AutoService).
Use getSupportedSourceVersion returning latestSupported() to avoid warnings on newer JDKs.
⚠ Follow-up traps
Why return false from process? Returning true claims the annotation so other processors skip it; false lets others see it too.
What if no explicit constructor exists? The compiler adds a default one, which appears in the enclosed elements.
#apt#processor#compile-time
Q77
Gradle build does not run Lombok after upgrading to Gradle 6+. Why?
intermediate
Since Gradle 5, processors on compileOnly are not executed. You must declare both:
Symptom: cannot find symbol: method getName() on Lombok-annotated classes.
⚠ Follow-up traps
Does Maven need the same? Maven picks processors from the classpath unless maven-compiler-pluginannotationProcessorPaths is set; then Lombok must be listed there.
Do tests need it separately? Yes, test source sets have their own configurations.
#lombok#gradle#apt
Q78
A Lombok @Data entity causes StackOverflowError in toString. Why?
intermediate
In a bidirectional relationship, Order.toString() prints its items, each Item.toString() prints its order, forever. Same for generated hashCode/equals.
@Data @Entity class Order { @OneToMany(mappedBy="order") List<Item> items; }@Data @Entity class Item { @ManyToOne Order order; }// order.toString() -> StackOverflowError
Fix: @ToString.Exclude / @EqualsAndHashCode.Exclude on the back-reference, or write equals/hashCode on the business key/ID manually.
⚠ Follow-up traps
Is @Data otherwise safe for DTOs? Yes, for simple classes; for entities prefer @Getter/@Setter only.
Does hashCode over a lazy collection trigger a query? Yes, loading the whole collection.
#lombok#jpa#bidirectional
Q79
A custom field annotation works on Java classes but not on a record. Why?
advanced
For a record component, an annotation is propagated only to the declaration contexts permitted by its @Target. If @Target(ElementType.FIELD) only, it lands on the private field; reading getRecordComponents()[0].getAnnotation finds nothing, and reading the accessor finds nothing.
Add RECORD_COMPONENT, METHOD, PARAMETER to @Target to expose it where the framework looks.
⚠ Follow-up traps
If @Target is absent? The annotation propagates to all applicable places.
Which element does Jackson read for records? The canonical constructor/accessors, so @JsonProperty needs RECORD_COMPONENT/PARAMETER-compatible targets (it has them).
#records#annotations#target
Q80
Spring bean created with `new` ignores @Value and @Autowired. Explain.
basic
Annotations are only processed for beans created by the container. new Mailer() skips BeanPostProcessors, so @Value/@Autowired fields stay null/default and @Transactional has no proxy.
Fixes: declare it as a bean and inject it, use ObjectProvider/ApplicationContext.getBean, or AutowireCapableBeanFactory.autowireBean(obj) for externally created objects.
Objects created by JPA, Jackson, or Kafka deserializers are likewise not injected.
⚠ Follow-up traps
Can @Configurable fix it? Yes with AspectJ weaving (@EnableSpringConfigured), but it needs agent/compile weaving.
Does @Component on the class alone suffice for new? No.
#spring#di#lifecycle
Q81
@PostConstruct is not called. Why?
intermediate
Likely causes: the bean is not Spring-managed; the jakarta.annotation API is missing; Boot 3 uses jakarta.annotation.PostConstruct, not javax; or the method has a parameter/is static.
In Spring 6, javax.annotation.PostConstruct is not recognized.
CommonAnnotationBeanPostProcessor handles it, called after dependency injection and before use.
Are @PostConstruct and constructor ordering defined? Constructor, then injection, then @PostConstruct.
Is @PreDestroy called for prototype beans? No, the container does not manage their destruction.
#spring#lifecycle#jakarta
Q82
Hibernate throws "No default constructor for entity". What does it mean?
basic
Hibernate instantiates entities reflectively and needs a no-argument constructor with at least package/protected visibility (public/protected per JPA spec).
@Entity class User { @Id Long id; String name; protected User() {} // for JPA public User(String name) { this.name = name; }}
⚠ Follow-up traps
Why protected, not private? JPA spec requires public or protected, and proxy subclasses need access.
Does @AllArgsConstructor alone satisfy it? No; add @NoArgsConstructor(access = PROTECTED).
#hibernate#entity#constructor
Q83
getDeclaredFields returns an unexpected `$jacocoData` field. What is it?
intermediate
JaCoCo's agent instruments bytecode and adds a synthetic static field $jacocoData and a method $jacocoInit. Reflection-based code (serializers, equals builders, schema generators) sees them.
Filter with field.isSynthetic() / method.isSynthetic().
Same story for inner classes' this$0 and enum $VALUES.
⚠ Follow-up traps
Does it appear in production? Only when the agent is attached, e.g. test runs.
Does Modifier.isTransient filter it? No, use isSynthetic.
#synthetic#jacoco#instrumentation
Q84
How would you implement a generic, annotation-driven CSV exporter?
intermediate
Define @Column(name, order), collect annotated fields once per class into a cached sorted list of Field (made accessible), then format rows using field.get.
@Retention(RUNTIME) @Target(FIELD) @interface Column { String value(); int order(); }static final ClassValue<List<Field>> FIELDS = new ClassValue<>() { protected List<Field> computeValue(Class<?> c) { return Arrays.stream(c.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(Column.class)) .sorted(Comparator.comparingInt(f -> f.getAnnotation(Column.class).order())) .peek(f -> f.setAccessible(true)).toList(); }};
ClassValue caches per class without leaking classloaders.
Escape quotes and commas; handle null.
⚠ Follow-up traps
Why ClassValue over a static HashMap<Class,...>? A strong map entry would prevent unloading the class and its loader.
Is getDeclaredFields order stable? Not guaranteed by spec, so sort explicitly.
#reflection#custom-annotation#design
Q85
A library uses reflection to read a field that was renamed by ProGuard/R8 obfuscation. What breaks?
intermediate
Names used via string lookups (getDeclaredField("email"), JSON property names derived from fields, Class.forName) no longer match, giving NoSuchFieldException or silently empty JSON. Add keep rules or use explicit names.
-keep class com.app.dto.** { *; }-keepattributes Signature,*Annotation*
Signature attribute must be kept or generic info (TypeToken) is lost.
Prefer @SerializedName/@JsonProperty("email") so names do not depend on field names.
⚠ Follow-up traps
Why are annotations missing after shrinking?-keepattributes *Annotation* was not set.
Why does Gson TypeToken throw after minification? The Signature attribute was stripped.
#obfuscation#keep-rules
Q86
What does this print: generic arrays and erasure with reflection?
intermediate
Both lists have the same runtime class, so the comparison is true.
List<String> a = new ArrayList<>();List<Integer> b = new ArrayList<>();System.out.println(a.getClass() == b.getClass()); // trueMethod m = Holder.class.getMethod("get"); // List<String> get()System.out.println(m.getReturnType()); // interface java.util.ListSystem.out.println(m.getGenericReturnType()); // java.util.List<java.lang.String>
Instance-level type args are erased; declaration-level signatures survive.
⚠ Follow-up traps
Can a.add((String)(Object)1) be blocked at runtime? Via reflection or raw types you can insert wrong types; failure appears later as ClassCastException.
Why can't you write new T[10]?T is erased; use Array.newInstance(clazz, n).
#generics#erasure#arrays
Q87
Implement a generic array creation using reflection.
basic
Use java.lang.reflect.Array.newInstance(componentType, length) with a Class<T> token.
@SuppressWarnings("unchecked")static <T> T[] newArray(Class<T> type, int n) { return (T[]) Array.newInstance(type, n);}String[] s = newArray(String.class, 3);System.out.println(s.getClass().getSimpleName()); // String[]
⚠ Follow-up traps
Why not (T[]) new Object[n]? It produces Object[], and returning it as String[] throws ClassCastException.
Does Array.newInstance(int.class, 3) work? Yes, returns int[] (boxed as Object).
#array#reflection#generics
Q88
Using reflection, how do you list all fields of an object including inherited private ones?
basic
getDeclaredFields is per class, so loop up the superclass chain.
static List<Field> allFields(Class<?> c) { List<Field> out = new ArrayList<>(); for (; c != null && c != Object.class; c = c.getSuperclass()) out.addAll(Arrays.asList(c.getDeclaredFields())); return out;}
Skip static and synthetic fields in most use cases.
Shadowed field names are possible, so key by declaring class too.
⚠ Follow-up traps
Why not getFields()? It returns only public fields (including inherited public ones).
What about interface constants?getFields() includes them; the loop above does not.
#field#inheritance
Q89
Debug: NoSuchMethodException when calling getMethod("process", Integer.class) for `process(int)`.
basic
getMethod matches exact parameter types: int.class is not Integer.class. Use getMethod("process", int.class); invoke accepts an Integer and unboxes it.
Method m = Svc.class.getMethod("process", int.class);m.invoke(svc, 5); // autoboxed arg, OK
There is no overload resolution like the compiler does; you must match the declared types exactly (or loop to find the best fit, as Spring's BeanUtils/MethodUtils do).
⚠ Follow-up traps
Does getMethod("process", Object.class) match process(String)? No, exact match only.
How do you get int.class from a wrapper?Integer.TYPE.
#getmethod#primitives
Q90
Which JDK version issues arise when a library uses sun.misc.Unsafe or internal APIs?
advanced
JDK 9 introduced strong encapsulation; JDK 16 closed java.* internals by default; 17 removed the escape flag. Libraries relying on sun.misc.Unsafe still work (jdk.unsupported), but those using jdk.internal.* or sun.nio.ch need --add-exports/--add-opens. JDK 23+ emits warnings for Unsafe memory access and later releases will deny it.
Diagnose with jdeps --jdk-internals app.jar.
Upgrade Lombok, Mockito, ByteBuddy, Kryo, Hadoop and Spark clients before moving JDK versions.
⚠ Follow-up traps
Can the --add-opens flags be put in the jar? Yes, via Add-Opens in the manifest of an executable jar.
Does an agent bypass encapsulation? Instrumentation agents can redefine classes and Instrumentation.redefineModule can open packages.
#migration#internals#java17
Q91
Implement equals-by-reflection helper and list the problems.
intermediate
A reflective equals iterates fields and compares with Objects.equals.
static boolean same(Object a, Object b) throws IllegalAccessException { if (a == b) return true; if (a == null || b == null || a.getClass() != b.getClass()) return false; for (Field f : a.getClass().getDeclaredFields()) { f.setAccessible(true); if (!Objects.equals(f.get(a), f.get(b))) return false; } return true;}
Problems: slow, ignores superclass fields, includes synthetic fields, breaks on arrays (Objects.equals is reference equality; use deepEquals), and fails under module encapsulation. Records and IDE-generated equals are better.
⚠ Follow-up traps
Does it break with $jacocoData or this$0? Yes, filter synthetic fields.
Does Apache EqualsBuilder.reflectionEquals avoid these? Partly, but it is still slow and reflection-dependent.
#reflection#equals#pitfalls
Q92
How would you implement a tiny JUnit-style test runner with annotations?
intermediate
Find methods annotated with @Check, instantiate the class, invoke each, count successes and failures, and unwrap InvocationTargetException.
Why create a new instance per method in JUnit? To avoid shared state between tests.
Is method execution order defined?getDeclaredMethods order is unspecified; JUnit 5 uses a deterministic but non-obvious order unless @TestMethodOrder.
#junit#reflection#custom-annotation
Q93
Is it a good idea to use reflection to copy properties between DTO and entity?
intermediate
Reflection mappers (BeanUtils.copyProperties, ModelMapper) are convenient but fail late: renamed fields silently stop copying. Compile-time generators (MapStruct) produce plain getter/setter code, giving type safety, speed and native-image compatibility.
BeanUtils.copyProperties ignores type mismatches silently (property skipped) and shallow-copies.
MapStruct errors at compile time for unmapped targets when unmappedTargetPolicy = ERROR.
⚠ Follow-up traps
Does BeanUtils.copyProperties(src, dst) copy nulls? Yes, overwriting existing values.
Is argument order the same in Apache and Spring BeanUtils? No, Spring is (source, target) and Apache Commons is (dest, orig).
#mapping#mapstruct#performance
Q94
Describe the effect of calling setAccessible(true) on a Method in a multi-module application where the target package is not opened.
advanced
setAccessible(true) throws InaccessibleObjectException (a RuntimeException) even though the member is public and the caller is a framework, if the declaring class is non-public or the package is not open/exported to the framework's module.
For public members of exported packages of public classes, no setAccessible is needed.
trySetAccessible() returns false instead of throwing, useful for frameworks that fall back.
Module.isOpen(pkg, other) can check beforehand; the owner can call module.addOpens(pkg, other) at runtime from inside the module.
⚠ Follow-up traps
Does canAccess(obj) replace isAccessible()? Yes, isAccessible is deprecated since 9.
Which opens form limits exposure? Qualified: opens com.app.model to org.hibernate.orm.core;.
#jpms#exceptions#framework
Q95
What is the output: inner class constructor and reflection?
advanced
A non-static inner class constructor takes the outer instance as a hidden first parameter.
class Outer { class Inner { Inner() {} } }Constructor<?>[] cs = Outer.Inner.class.getDeclaredConstructors();System.out.println(cs[0].getParameterCount()); // 1System.out.println(cs[0].getParameterTypes()[0]); // class OuterObject in = cs[0].newInstance(new Outer());
getDeclaredConstructor() (no args) throws NoSuchMethodException. Same issue breaks Jackson/Gson on non-static inner classes.
⚠ Follow-up traps
Why is making DTOs static nested classes recommended? They need no outer instance and instantiate cleanly via no-arg constructor.
What is the extra field in the inner class? The synthetic this$0.
#inner-class#constructor#reflection
Q96
A service using @Async or @Cacheable also fails when injected by class name. Why and how to fix?
intermediate
If the bean has interfaces and JDK proxying is selected (proxyTargetClass=false, e.g. via @EnableAsync(proxyTargetClass=false) or older Spring), the proxy implements the interface only, so injecting MyServiceImpl fails: BeanNotOfRequiredTypeException ... is actually of type jdk.proxy2.$Proxy45.
Fix: inject by interface, or force CGLIB with proxyTargetClass = true (Boot default).
⚠ Follow-up traps
What does Boot do by default?spring.aop.proxy-target-class=true, so CGLIB.
Does @Async on a private method run async? No, it runs synchronously on the caller thread.
#proxy#spring#injection
Q97
A custom @Cached annotation using Spring AOP caches wrong results for different arguments. Why?
intermediate
The aspect likely used only the method name as the key. A correct key combines the target class, method signature and the arguments (Arrays.deepHashCode/SimpleKey) or a SpEL expression from the annotation.
Beware mutable arguments (key changes), null handling, and unbounded growth; use Caffeine with a size/TTL.
⚠ Follow-up traps
Does toString() of an arbitrary object work as a key? Not unless it renders state; default Object.toString includes identity hash.
How does Spring's @Cacheable build keys?SimpleKeyGenerator over the parameters, or a key SpEL expression.
#aop#cache#key
Q98
How do you detect at startup that a required annotation's attribute is misconfigured, instead of failing at runtime?
intermediate
Validate in a BeanPostProcessor/SmartInitializingSingleton or an ApplicationRunner that scans beans for the annotation and throws IllegalStateException on invalid attributes, so the app refuses to start. At build time, an annotation processor can do the same with compiler errors.
Spring Boot's @ConfigurationProperties + @Validated validate config binding at startup.
Fail-fast avoids production surprises on first request.
⚠ Follow-up traps
Why not validate inside the aspect on first call? The failure shows up only when that path is hit in production.
Does @Validated on @ConfigurationProperties need a validator on the classpath? Yes, spring-boot-starter-validation.
#fail-fast#spring#validation
Q99
What happens if two threads call Field.set on the same shared object reflectively?
intermediate
Reflection gives no synchronization or visibility guarantees beyond those of the field itself. Field.set on a non-volatile field is a plain write; another thread may see a stale value. Field objects themselves are thread-safe to share for reads, but setAccessible state is per Field instance and a copy is returned by each getDeclaredField.
Use VarHandle or AtomicXFieldUpdater for atomic updates.
Field.set on a volatile field honours volatile semantics.
⚠ Follow-up traps
Does AtomicIntegerFieldUpdater use reflection? It uses reflection (and now VarHandle-like intrinsics) to access a volatile int field, with checks that the field is volatile and accessible.
Is getDeclaredField returning the same instance? No, a fresh copy each call (root object is internal).
#concurrency#reflection
Q100
When would you NOT use reflection or annotations?
intermediate
Avoid them when a plain interface, lambda or explicit code gives the same result with static checking.
Reflection hides dependencies, bypasses encapsulation, defeats refactoring tools, hurts native image and obfuscation, and costs performance.
Annotation-heavy "magic" (stacked @Transactional, @Async, @Cacheable on one method) creates ordering bugs and hard-to-debug proxies.
Is framework use of reflection a reason to avoid frameworks? No, but isolate it at the edges and keep domain code free of it.
How do you make annotation behaviour testable? Test the aspect/processor in isolation and add an integration test proving the proxy applies.
#design#trade-offs
Q101
Convert a Map<String,Object> into a POJO using reflection (simple binder). What are the pitfalls?
intermediate
Create the object via no-arg constructor, loop over fields, find the key, convert, and set.
static <T> T bind(Map<String, Object> m, Class<T> c) throws Exception { T o = c.getDeclaredConstructor().newInstance(); for (Field f : c.getDeclaredFields()) { if (!m.containsKey(f.getName())) continue; f.setAccessible(true); Object v = m.get(f.getName()); if (f.getType() == int.class && v instanceof String s) v = Integer.parseInt(s); f.set(o, v); } return o;}
Pitfalls: type conversion (String -> int, enum, date, nested), final fields, setter logic bypassed, unknown keys silently ignored, IllegalArgumentException when types mismatch (Field.set unwraps/widens only).
⚠ Follow-up traps
Does Field.set convert Integer to long field? Widening of unwrapped primitives is allowed (Integer to long), but not String to anything.
Does it call setter validation? No, it bypasses setters; use BeanWrapper for property semantics.
#binder#reflection#type-conversion
Q102
Why do "annotation on private field, accessed via getter" frameworks sometimes fail on Kotlin or Lombok classes?
advanced
Annotations placed on a field are not copied to generated getters/setters by default. Lombok's @Getter copies only some known annotations (e.g. @Nullable, Jackson ones in copyableAnnotations); Kotlin applies an annotation to the first applicable target (param, property, field) unless you write @get:/@field:.
data class U(@field:NotBlank val name: String) // Kotlin: pick explicit target
For Lombok, add lombok.copyableAnnotations += com.app.MyAnno to lombok.config.
⚠ Follow-up traps
Why does Bean Validation in Kotlin say "no constraint found"? The annotation went to the constructor parameter by default; use @field: or @get:.
Does the Lombok config apply to the whole module? The nearest lombok.config file up the directory tree.
#lombok#kotlin#annotation-propagation
Q103
How does JUnit's @ParameterizedTest with @MethodSource find a factory method?
intermediate
JUnit reads the @MethodSource("names") attribute and reflectively looks up a method by that name in the test class (or fully qualified Class#method), invokes it (static unless PER_CLASS lifecycle), and converts the returned Stream/Iterable/array into invocation arguments.
With no name, it looks for a method with the same name as the test method.
Argument conversion is implicit (String to enum, number widening) and explicit via ArgumentConverter.
⚠ Follow-up traps
Why "must be static" error? Default lifecycle creates a new instance per test method, so the factory must be static (unless @TestInstance(PER_CLASS)).
Can the provider be private? JUnit uses reflection and handles it, but it is recommended to be package-private or higher.
#junit#reflection#method-source
Q104
Compare how Spring, Quarkus and Micronaut each use annotations at build vs runtime.
advanced
Classic Spring (pre-AOT) processes annotations reflectively at startup. Micronaut uses annotation processors at compile time to generate bean definitions, with no reflection or runtime proxies. Quarkus runs a build-time augmentation step with Jandex indexing and bytecode recording; Spring 6 AOT moves closer to this model.