skip to content

Resources & Serialization

Classpath resources, resource bundles, Java serialization and JNI all have to be declared explicitly or they are simply absent from the image. Interviewers use a missing template or message bundle as the classic symptom.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In a GraalVM native image, why do classpath resources (like a .txt or .sql file loaded via ClassPathResource) sometimes fail to load at runtime, and what fixes it?

level: juniorimportance: must knowfreq 70%

answer

  1. Closed-world = only build-time-reachable embedded
  2. getResourceAsStream returns null in native
  3. ResourceHints.registerPattern / resource-config.json
  4. @ImportRuntimeHints + RuntimeHintsRegistrar
  5. JVM run passes, native fails — test the image

basics

~20 s

GraalVM's closed-world build only bundles resources it knows about. Files loaded reflectively at runtime aren't detected, so they're dropped. You must register them (via resource-config.json or Spring ResourceHints) so the build includes them in the image.

solid answer

~40 s

A native image is built ahead-of-time under a closed-world assumption: only code and resources reachable at build time are embedded; everything else is excluded to keep the image small. Classpath resources loaded dynamically — e.g. `getClass().getResourceAsStream(name)` or Spring's `ClassPathResource`/`ResourceLoader` — often can't be traced statically, so they're not included and `getResource` returns null at runtime, typically surfacing as a FileNotFound-style failure. The fix is to declare them so the build embeds them: GraalVM's native `resource-config.json` (patterns of resource paths to include), or, in Spring, register a `ResourceHints` entry — usually via a `RuntimeHintsRegistrar` with `hints.resources().registerPattern("config/*.sql")`. Spring Boot's AOT engine registers many resources automatically, but app-specific ad-hoc resources still need explicit hints.

code

java · 17 lines
java
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;

@Configuration
@ImportRuntimeHints(AppResourceHints.MyHints.class)
class AppResourceHints {
    static class MyHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            // Ant-style pattern; embeds every matching file into the image
            hints.resources().registerPattern("db/migration/*.sql");
            hints.resources().registerPattern("templates/email.html");
        }
    }
}

go deeper

for a junior

Know the phrase 'closed-world' and that dynamically loaded resources must be registered or they go missing.

for a middle

Can write a RuntimeHintsRegistrar with registerPattern and wire @ImportRuntimeHints.

for a senior

Explains the JVM-passes/native-fails trap and uses the tracing agent to bootstrap config.

for a principal

Weighs Spring hints vs raw JSON, knows glob-vs-regex pattern syntax difference and what Boot auto-registers.

## The core problem: closed-world assumption A **GraalVM native image** is a standalone executable produced by ahead-of-time (AOT) compiling your application. Unlike the JVM, which loads classes and resources lazily at runtime, the native-image builder analyzes the whole program **at build time** and embeds only what it can prove is reachable. This is the **closed-world assumption**: anything the static analysis doesn't see is left out of the final binary. This applies to reflection, proxies, JNI, serialization — and **resources**. ## What is a 'resource' here? A resource is any non-class file on the classpath: `application.yml`, `messages.properties`, `schema.sql`, templates, JSON, images, etc. On the JVM you read them with `Class.getResourceAsStream(...)`, `ClassLoader.getResource(...)`, or Spring's `ClassPathResource`/`ResourceLoader`. Because these are looked up **by name (a String) at runtime**, the builder generally cannot know which files you'll ask for, so it does **not** embed them by default. At runtime `getResourceAsStream` then returns `null`. ## Two ways to register resources **1. GraalVM native config — `resource-config.json`** (lives under `META-INF/native-image/<group>/<artifact>/`). You list glob-like patterns: ```json { "resources": { "includes": [ { "pattern": "config/.*\\.sql" } ] } } ``` The builder then copies matching files into the image. **2. Spring's programmatic hints — `ResourceHints`.** Spring's AOT layer generates the GraalVM JSON for you from Java-defined **RuntimeHints**. You implement `RuntimeHintsRegistrar`: ```java class MyHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, ClassLoader cl) { hints.resources().registerPattern("config/*.sql"); } } ``` and wire it with `@ImportRuntimeHints(MyHints.class)` on a `@Configuration` class. This is the idiomatic Spring approach and keeps the hint next to your code. ## What Spring Boot does automatically Spring Boot's AOT processing registers a large set of resources for you: `application.properties`/`application.yml` and profile variants, `banner.txt`, `META-INF/spring.factories`, `git.properties`/`build-info.properties`, logging configs, and resources referenced by known infrastructure. So you usually only need manual hints for **your own ad-hoc resources** loaded by a path the framework doesn't know about. ## Gotchas - **Directory listing doesn't work.** Native images don't support enumerating a classpath directory; you must register concrete files/patterns. - **Patterns differ.** `resource-config.json` uses regex; Spring's `registerPattern` uses Ant/glob-style (`*`, `**`, `?`). Don't mix syntaxes. - **Missing-resource symptoms are silent-ish.** You get a `null` stream or FileNotFound only at runtime in the native binary — the JVM run passes, hiding the bug. Always test the actual native image (or use `RuntimeHintsPredicates` in a unit test). - **The GraalVM tracing agent** (`-agentlib:native-image-agent`) run on the JVM can auto-capture accessed resources into config JSON — useful to bootstrap hints. ## When to use what Prefer Spring `RuntimeHintsRegistrar` + `@ImportRuntimeHints` inside a Spring app (portable, colocated, testable). Drop to hand-written `resource-config.json` for non-Spring libraries or when a third-party jar needs hints you ship yourself.

  • Your app works on the JVM but the native binary throws because a .sql file is missing. What's the fastest way to discover which resources are actually accessed?
    Run the app on a normal JVM with the GraalVM tracing agent (`-agentlib:native-image-agent=config-output-dir=...`). It records every resource/reflection/proxy access and writes `resource-config.json` you can review and trim, then feed into the build.
  • Does Spring register application.yml automatically, and why does that matter?
    Yes — Spring Boot's AOT processing auto-registers config files (application.properties/yml and profile variants), banner.txt, spring.factories, etc. That's why config loads in native without manual hints; you only add hints for your own non-standard resources.

context

open as a page

How do you make Java resource bundles (i18n messages) work in a GraalVM native image with Spring, and how does that differ from registering a plain classpath resource?

level: middleimportance: should knowfreq 40%

basics

~20 s

Resource bundles (message .properties per locale) need their own registration, not just a file pattern, because ResourceBundle resolves by base name plus locale. Use ResourceHints.registerResourceBundle(baseName) or GraalVM's resource-config.json 'bundles' section, so all locale variants are embedded.

open as a page

When does a GraalVM native image need Java serialization hints (serialization-config.json / SerializationHints), and how do you register them in Spring?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Only classes actually put through Java's ObjectInputStream/ObjectOutputStream need serialization hints. GraalVM excludes serialization metadata by default. In Spring, call hints.serialization().registerType(Foo.class) in a RuntimeHintsRegistrar, or list the class in serialization-config.json.

open as a page

How would you design a reliable workflow to catch missing resource/serialization hints for a native image before it reaches production, given JVM tests don't exercise them?

level: principalimportance: should knowfreq 25%

basics

~20 s

Combine three layers: unit-test hints with RuntimeHintsPredicates on the JVM, use the GraalVM tracing agent to capture real accesses into config, and run an actual native-image build in CI plus a smoke test of the binary so missing-resource failures surface before prod.

open as a page