🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
2026-02-20 22:10:40.100 ERROR 8842 --- [ main] o.s.boot.SpringApplication : Application run failed
com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.devinhyderabad.service.UserService] not found in the image heap. Ensure the proxy class is registered for reflection.
at java.base/java.lang.reflect.Proxy.getProxyClass(Proxy.java:322)⚡ Quick Fix Works 80% of the time
Register a proxy hint for the interface using RuntimeHints.
@Component
public class ProxyHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.proxies().registerJdkProxy(UserService.class);
}
}🧠 Why this Happens
Tap to expand the deep technical explanation
Spring AOP often uses JDK dynamic proxies to wrap your `@Service` interfaces for things like `@Transactional`. GraalVM cannot generate new classes at runtime. If the proxy class wasn't generated and registered during the AOT build phase, GraalVM crashes when Spring tries to create it at runtime.
The HITEC City Parking Spot Analogy:
It's like a tailor trying to sew a custom suit (Proxy) at runtime, but the sewing machine (GraalVM) was locked away at the factory (Build Time). The tailor can't make the suit because they don't have the machine.
🔁 How to Reproduce Confirm this is your error
Build a Spring Boot Native Image. Create an interface `UserService` and annotate the implementation with `@Service` and `@Transactional`. Do not provide any proxy hints. Run the native binary.
🛠️ Solutions (5 Ways to Fix)
Register JDK Proxy Hint
👉 Use this when Spring fails to proxy an interface in Native Image.
Tell Spring's AOT engine to generate the proxy class for this interface at build time.
@Component
public class ProxyHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.proxies().registerJdkProxy(UserService.class);
}
}Use CGLIB proxies instead of JDK proxies
👉 Use this if you don't want to register hints for every interface.
If you don't implement an interface, Spring uses CGLIB to proxy the actual class. Spring Boot 3's AOT engine handles CGLIB proxies automatically.
// Remove the interface
@Service
@Transactional
public class UserService { ... } // No interfaceDisable proxyTargetClass
👉 Use this if you explicitly forced JDK proxies.
If you set `spring.aop.proxy-target-class=false`, Spring uses JDK proxies. Remove the property to default to CGLIB.
# application.properties
# Remove this line:
# spring.aop.proxy-target-class=falseUse GraalVM Tracing Agent
👉 Use this if a third-party library creates the proxy internally.
Run the app on standard JVM with the agent to capture all proxy creation automatically.
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/ -jar target/myapp.jarAvoid @Transactional on private methods
👉 Use this if your proxy fails on a specific method.
Spring AOP proxies cannot intercept private methods. Ensure `@Transactional` is only on public methods.
@Service
public class UserService {
@Transactional
public void save() { ... } // Public, works
}📋 Version Notes
Native image support was experimental. Proxy config was manual.
AOT engine registers CGLIB proxies automatically, but JDK proxies require manual hints.
🛡️ How to Prevent This Next Time
Prefer concrete classes over interfaces for Spring services if you plan to build GraalVM Native Images. This forces CGLIB proxies, which Spring Boot 3's AOT engine handles flawlessly without manual hints.