🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG2026-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)

Solution 1✓ Most common cause

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); } }
Solution 2

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 interface
Solution 3

Disable 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=false
Solution 4

Use 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.jar
Solution 5

Avoid @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

Spring Boot 2.x

Native image support was experimental. Proxy config was manual.

Spring Boot 3.x

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.