How do ResourceHints handle static resources versus resource bundles in a native image?
answer
- resources() → resource-config.json
- registerPattern for templates/SQL/txt
- registerResourceBundle(baseName) for i18n
- Bundle = locale-aware, base name not glob
- Non-class files excluded by default
basics
~10 shints.resources().registerPattern("...") bundles matching files (templates, properties) into the image. hints.resources().registerResourceBundle("messages") includes an i18n ResourceBundle base name. Both go into resource-config.json.
solid answer
~40 s`ResourceHints`, via `hints.resources()`, controls two related things. First, arbitrary classpath **resources**: `registerPattern("db/migration/*.sql")` or `registerResource(new ClassPathResource(...))` tells the native image to embed those files so `getResourceAsStream` works at runtime — otherwise they're absent because they aren't code. Second, **resource bundles** for i18n: `registerResourceBundle("messages")` registers a `ResourceBundle` base name so `ResourceBundle.getBundle("messages", locale)` resolves the localized `.properties` variants. Resource bundles are a distinct concept because GraalVM handles them specially (it needs the base name plus locale-specific files), which is why Spring exposes a dedicated method even though both feed the same `resource-config.json`. You register bundles rather than just patterns so all locale suffixes are captured correctly.
code
java · 15 linesimport org.springframework.aot.hint.*;
import org.springframework.core.io.ClassPathResource;
public class ResourceHintsExample implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader cl) {
// Plain resources embedded so getResourceAsStream works
hints.resources().registerPattern("templates/*.mustache");
hints.resources().registerResource(new ClassPathResource("banner.txt"));
// i18n bundles: register the BASE NAME, not a file glob
hints.resources().registerResourceBundle("messages");
hints.resources().registerResourceBundle("ValidationMessages");
}
}go deeper
Know templates/properties must be registered so getResourceAsStream works in native.
Distinguish registerPattern (plain files) from registerResourceBundle (i18n base name) and know both feed resource-config.json.
Explain locale fallback semantics and why bundles get a dedicated method; scope patterns to avoid bloat.
Weigh binary-size vs coverage, and design a hint strategy for many locales / third-party message sources.
## The two responsibilities of `ResourceHints` `org.springframework.aot.hint.ResourceHints` (from `RuntimeHints.resources()`) covers everything that is a *file on the classpath* rather than compiled code. In a native image, non-class resources are **not** included by default, so any `getResourceAsStream`, template load, or SQL migration read would fail unless declared. It writes to GraalVM's `resource-config.json`. ### 1. Plain resources (patterns) ```java hints.resources().registerPattern("templates/*.mustache"); hints.resources().registerPattern("db/migration/V*.sql"); hints.resources().registerResource(new ClassPathResource("banner.txt")); ``` `registerPattern` takes a pattern (with `*` wildcards) matched against classpath resource paths; matching files get embedded in the binary. `registerResource(Resource)` embeds a single known resource. There's also `registerPattern(ResourcePatternHint...)` style building for include/exclude patterns. ### 2. Resource bundles (i18n) Java internationalization uses `ResourceBundle.getBundle("messages", locale)`, which at runtime looks up `messages.properties`, `messages_fr.properties`, `messages_de_DE.properties`, etc. GraalVM treats bundles as a **special category** — you register a *base name*, and the toolchain arranges for all locale variants to be reachable: ```java hints.resources().registerResourceBundle("messages"); hints.resources().registerResourceBundle("validation/ValidationMessages"); ``` This is the concept the leaf calls 'ResourceBundleHints' — it isn't a separate top-level sub-API but a dedicated method on `ResourceHints`, because bundle resolution is locale-aware and GraalVM models it distinctly (in the classic format, bundles appear in a `bundles` section of `resource-config.json`). ## Why the distinction matters If you registered `messages*.properties` as a raw *pattern*, the files would be embedded, but you'd bypass GraalVM's bundle-awareness (locale fallback chains, `ResourceBundle.Control`, etc.). Using `registerResourceBundle` records the base name so bundle lookup semantics are preserved. Conversely, a bundle registration is the wrong tool for a non-`.properties` resource like an HTML template — use a pattern there. ## When you need these - Bean Validation messages (`ValidationMessages.properties`) — often needs a bundle hint in native - Custom `messages.properties` for `MessageSource` - Template engines reading `.mustache`/`.ftl`/`.html` from the classpath - Flyway/Liquibase-style SQL migration files, `.json`/`.yaml` config read via `getResourceAsStream` ## Gotchas - Spring Boot auto-registers many common resources, but *your* custom paths and third-party bundles frequently need manual hints. - Patterns match resource *paths*, not filesystem globs — get the classpath-relative path right. - A missing resource hint surfaces at runtime as a `null` stream or `MissingResourceException`, not a build error. - Over-broad patterns (e.g. `**/*`) bloat the binary — scope them.
- Why register a resource bundle by base name instead of registering messages*.properties as a pattern?Bundle resolution is locale-aware: getBundle walks a fallback chain (messages_fr_FR → messages_fr → messages). Registering the base name preserves that bundle semantics and the locale variants; a raw pattern just embeds files and can miss GraalVM's bundle handling. Base name is the correct abstraction for i18n.
- A .sql migration file read via getResourceAsStream returns null in native but works on the JVM — cause?The file wasn't embedded in the image because non-class resources are excluded by default. Add hints.resources().registerPattern("db/migration/V*.sql") (or the exact classpath path) so it's bundled into the binary.
saying these in an interview costs you the question
- Assuming classpath resources are automatically included in native images
- Registering i18n .properties as a plain pattern and losing locale fallback
- Thinking ResourceBundleHints is a separate top-level sub-API rather than a method on ResourceHints