or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-config.mdaot.mdcaching.mdcontext-lifecycle.mdevents.mdformatting.mdi18n.mdindex.mdjmx.mdresilience.mdscheduling.mdstereotypes.mdvalidation.md
tile.json

aot.mddocs/

AOT Compilation and Native Image Support

AOT Processing

// Build-time AOT generation
public class MyAotProcessor extends ContextAotProcessor {

    public MyAotProcessor(Class<?> mainClass) {
        super(mainClass, Settings.builder()
            .sourceOutput(Paths.get("target/spring-aot/main/sources"))
            .resourceOutput(Paths.get("target/spring-aot/main/resources"))
            .classOutput(Paths.get("target/spring-aot/main/classes"))
            .groupId("com.example")
            .artifactId("my-app")
            .build());
    }

    @Override
    protected GenericApplicationContext prepareApplicationContext(Class<?> appClass) {
        GenericApplicationContext context = new GenericApplicationContext();
        new AnnotatedBeanDefinitionReader(context).register(appClass);
        return context;
    }
}

// Execute AOT processing
MyAotProcessor processor = new MyAotProcessor(MyApplication.class);
ClassName initializer = processor.process();

Runtime Native Image Execution

public class MyApplication {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // Load AOT-generated initializer
        AotApplicationContextInitializer<GenericApplicationContext> initializer =
            AotApplicationContextInitializer.forInitializerClasses(
                "com.example.__ApplicationContextInitializer"
            );

        // Initialize with AOT artifacts
        initializer.initialize(context);
        context.refresh();

        // Run application
    }
}

@ImportRuntimeHints

@Configuration
@ImportRuntimeHints(MyRuntimeHints.class)
public class AppConfig {}

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Reflection hints
        hints.reflection().registerType(
            MyClass.class,
            MemberCategory.INVOKE_PUBLIC_METHODS,
            MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
        );

        // Resource hints
        hints.resources().registerPattern("config/*.properties");
        hints.resources().registerPattern("templates/*.html");

        // Serialization hints
        hints.serialization().registerType(MySerializableClass.class);

        // JNI hints
        hints.jni().registerType(NativeLibrary.class);

        // Proxy hints
        hints.proxies().registerJdkProxy(MyInterface.class);
    }
}

@Reflective and @ReflectiveScan

// Mark for reflective access
@Reflective
@Component
public class MyReflectiveComponent {

    @Reflective
    public void invokedReflectively() {}
}

// Enable package scanning
@Configuration
@ReflectiveScan(basePackages = "com.example.reflective")
public class ReflectiveConfig {}

Custom AOT Processor

@Component
public class CustomAotProcessor implements BeanFactoryInitializationAotProcessor {

    @Override
    public BeanFactoryInitializationAotContribution processAheadOfTime(
            ConfigurableListableBeanFactory beanFactory) {

        List<String> customBeans = findCustomBeans(beanFactory);

        if (customBeans.isEmpty()) {
            return null;
        }

        return (generationContext, beanFactoryInitializationCode) -> {
            // Generate initialization code
            GeneratedMethod method = beanFactoryInitializationCode
                .getMethods()
                .add("initCustomBeans", builder -> {
                    builder.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
                    builder.addParameter(DefaultListableBeanFactory.class, "beanFactory");

                    for (String beanName : customBeans) {
                        builder.addStatement("// Initialize: $L", beanName);
                    }
                });

            beanFactoryInitializationCode.addInitializer(method.toMethodReference());

            // Register runtime hints
            customBeans.forEach(bean -> {
                generationContext.getRuntimeHints()
                    .reflection()
                    .registerType(TypeReference.of("com.example." + bean),
                        hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
            });
        };
    }

    private List<String> findCustomBeans(ConfigurableListableBeanFactory beanFactory) {
        return Arrays.stream(beanFactory.getBeanDefinitionNames())
            .filter(this::isCustomBean)
            .collect(Collectors.toList());
    }
}

Register AOT Processor

# META-INF/spring/aot.factories
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
com.example.CustomAotProcessor

Bean Registration AOT Processor

@Component
public class CustomBeanRegistrationProcessor implements BeanRegistrationAotProcessor {

    @Override
    public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {

        if (!isCustomBean(registeredBean)) {
            return null;
        }

        return (generationContext, beanRegistrationCode) -> {
            // Generate bean instantiation code
            GeneratedMethod factory = beanRegistrationCode
                .getMethods()
                .add("create", builder -> {
                    builder.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
                    builder.returns(registeredBean.getBeanClass());
                    builder.addStatement("return new $T()", registeredBean.getBeanClass());
                });

            beanRegistrationCode.addInstancePostProcessor(factory.toMethodReference());

            // Register reflection hints
            generationContext.getRuntimeHints()
                .reflection()
                .registerType(registeredBean.getBeanClass(),
                    MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
        };
    }
}

Best Practices for Native Images

// Use constructor injection (no reflection needed)
@Component
public class MyService {
    private final MyRepository repository;

    public MyService(MyRepository repository) {  // Detected by AOT
        this.repository = repository;
    }
}

// Avoid field injection (requires reflection hints)
// @Autowired private MyRepository repository;  // Requires hint

// Provide explicit hints for dynamic behavior
@Configuration
@ImportRuntimeHints(DataAccessHints.class)
public class DataConfig {

    public static class DataAccessHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            hints.reflection().registerType(DynamicEntity.class,
                MemberCategory.INVOKE_PUBLIC_METHODS);
            hints.resources().registerPattern("sql/*.sql");
        }
    }
}

Testing AOT Processing

@SpringBootTest
@EnabledInNativeImage  // Only run in native image
class NativeImageTests {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        assertThat(context).isNotNull();
    }

    @Test
    void beansAreRegistered() {
        MyService service = context.getBean(MyService.class);
        assertThat(service).isNotNull();
    }
}

Native Image Configuration

# Enable verbose GraalVM output
native-image \
  --verbose \
  -H:+PrintClassInitialization \
  -H:+PrintFeatures \
  -jar my-app.jar

# Add native-image.properties in META-INF/native-image/
Args = --initialize-at-build-time=com.example.config
Args = --initialize-at-run-time=com.example.runtime