skip to content

What does reflect-config.json declare, and what is the difference between a class being 'included' and being 'registered for reflection'?

level: seniorimportance: should knowfreq 45%

answer

  1. JSON lists classes + member categories
  2. allDeclaredConstructors/Methods/Fields or named
  3. included = code present; registered = metadata kept
  4. constructor switch separate from methods
  5. tracing agent / Spring AOT generate it

basics

~20 s

reflect-config.json lists classes and which reflective members (constructors, methods, fields) to keep metadata for. Being 'included' means the class is in the image; being 'registered for reflection' means its member metadata is retained so reflective lookup/invocation works.

solid answer

~40 s

reflect-config.json is GraalVM's native-image configuration that names classes and the categories of reflective access they need — e.g. allDeclaredConstructors, allDeclaredMethods, allDeclaredFields, or specific methods/fields by name. The builder reads it and (a) ensures the class is in the image and (b) retains the requested reflection metadata. These are separate: a class can be included because ordinary code references it, yet still lack reflection metadata, so getDeclaredMethods() or newInstance() fails. Registration is what makes the enumerable metadata and reflective invocation available. In Spring you almost never write this JSON by hand — Spring AOT emits equivalent reachability metadata during the build for framework patterns — but the JSON (or the tracing agent that generates it) is the underlying contract you sometimes fall back to for your own dynamic reflection.

code

java · 13 lines
java
// reflect-config.json fragment for a class we build reflectively:
// [
//   {
//     "name": "com.acme.PluginHandler",
//     "allDeclaredConstructors": true,   // needed for newInstance()
//     "allDeclaredMethods": true         // needed for invoke()
//   }
// ]

// Without allDeclaredConstructors, this line fails even if the class is included:
Object h = Class.forName("com.acme.PluginHandler")
                .getDeclaredConstructor()   // NoSuchMethodException / missing registration
                .newInstance();

go deeper

for a junior

Aware reflect-config.json exists and lists classes needing reflection.

for a middle

Can read the JSON and knows registration keeps metadata.

for a senior

Articulates included-vs-registered clearly and the separate constructor/method/field switches; knows the tracing agent and Spring AOT paths.

for a principal

Balances broad vs. specific registration against image size, and designs where hand hints vs. automatic Spring AOT hints belong.

## What reflect-config.json is `reflect-config.json` is one of GraalVM native-image's **configuration files**, conventionally placed under `META-INF/native-image/<group>/<artifact>/`. It is a JSON array where each entry names a class and declares *which reflective capabilities* the image must retain for it. Typical keys: ```json [ { "name": "com.acme.PluginHandler", "allDeclaredConstructors": true, "allDeclaredMethods": true, "allDeclaredFields": true }, { "name": "com.acme.Dto", "fields": [ { "name": "id" }, { "name": "name" } ], "methods": [ { "name": "getId", "parameterTypes": [] } ] } ] ``` You can request broad access (`allDeclaredMethods`) or pin specific members. Broad access is convenient but bloats the image and its metadata; specific members are leaner. ## 'Included' vs 'registered for reflection' — the key distinction These are two orthogonal facts about a class in a native image: 1. **Included (reachable):** the class's *code* is compiled into the binary because static analysis found ordinary (non-reflective) usage of it — a `new`, a typed call, a field of that type. Without inclusion you get `ClassNotFoundException`. 2. **Registered for reflection:** the builder additionally retains the class's **reflection metadata** — the enumerable list of constructors/methods/fields and the ability to invoke them by name. Without registration, `getDeclaredMethods()` returns an empty/partial set, `getDeclaredField("x")` throws `NoSuchFieldException`, and `getDeclaredConstructor().newInstance()` throws `MissingReflectionRegistrationError`. A class can be **included but not registered**: present in the image, invokable through normal typed calls, yet invisible to reflection. reflect-config.json (or hints) is what flips on registration. Conversely, registering a class for reflection also forces its inclusion, since you can't reflect over absent code. ## How this maps to Spring Writing this JSON by hand is error-prone, so two tooling paths exist: - **The GraalVM tracing agent** (`-agentlib:native-image-agent`) — you run the app or its tests on a normal JVM, and the agent records every reflective access, generating `reflect-config.json` for you. - **Spring AOT** — during a native build Spring analyses beans, `@ConfigurationProperties`, Jackson-bound DTOs, JPA entities, `@Controller` argument types, etc., and programmatically registers the equivalent metadata. This is the everyday mechanism; you rarely touch JSON. (The internals of *how* Spring registers hints belong to the reachability-metadata topic; here we only note that they compile down to the same reflect-config contract.) ## Gotchas - **Over-registration**: `allPublicMethods` on huge type hierarchies inflates binary size and metadata; prefer specific members where practical. - **Constructors vs. methods vs. fields are separate switches** — registering methods does not register the no-arg constructor `newInstance()` needs; a very common cause of `newInstance` failing. - **Serialization and proxies have their own config files** (`serialization-config.json`, `proxy-config.json`); reflect-config alone doesn't cover them. - **Your DTOs vs. library DTOs**: Jackson needs *your* DTO fields/getters registered; registering Jackson itself is not enough. ## When to reach for it Only when Spring AOT's automatic hints miss a case — typically your *own* code doing string-driven `Class.forName`, hand-rolled plugin loading, or reflecting over types Spring can't infer. Then you supply a hint (preferred, via Spring's API) or, at the lowest level, a reflect-config.json entry.

  • Why might newInstance() fail even after you registered the class's methods?
    Because constructor registration is a separate switch. allDeclaredMethods retains method metadata but not the no-arg constructor; you also need allDeclaredConstructors (or the specific constructor) for getDeclaredConstructor().newInstance() to work.
  • How can you generate reflect-config.json instead of hand-writing it?
    Run the app or its test suite on a JVM with the GraalVM native-image tracing agent, which records reflective accesses and emits the config. In Spring, prefer letting Spring AOT register hints automatically and only supplement for your own dynamic reflection.

saying these in an interview costs you the question

  • Believing inclusion of a class automatically enables reflection over it
  • Registering methods and expecting newInstance() to work without constructor registration
  • Thinking reflect-config also covers serialization and dynamic proxies

context