spring-cloud-openfeign: AOT/native compilation not working with FeignClient and AOP

Describe the bug I read that starting spring-cloud 2022.0.0, you can use native compilation. I’m using 2022.0.2 (spring-cloud-starter-openfeign:4.0.2), but when I use an OpenFeign client with native compilation, I get the following error:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'boxedHelloController': Unsatisfied dependency expressed through field 'helloWorldClient': Error creating bean with name 'nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient': Post-processing of FactoryBean's singleton object failed
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:195) ~[na:na]
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveAndSet(AutowiredFieldValueResolver.java:167) ~[na:na]
        at nl.cqit.function.poc.java.boxedhello.controller.BoxedHelloController__Autowiring.apply(BoxedHelloController__Autowiring.java:15) ~[na:na]
        at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:83) ~[na:na]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[boxedhello-app.exe:3.0.5]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[boxedhello-app.exe:3.0.5]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[boxedhello-app.exe:3.0.5]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[boxedhello-app.exe:3.0.5]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[boxedhello-app.exe:3.0.5]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[boxedhello-app.exe:3.0.5]
        at nl.cqit.function.poc.java.boxedhello.Main.main(Main.java:12) ~[boxedhello-app.exe:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient': Post-processing of FactoryBean's singleton object failed
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:108) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1823) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1273) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:259) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1640) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1597) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:189) ~[na:na]
        ... 22 common frames omitted
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:171) ~[na:na]
        at java.base@19.0.2/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[boxedhello-app.exe:na]
        at java.base@19.0.2/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[boxedhello-app.exe:na]
        at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123) ~[na:na]
        at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
        at org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization(AbstractAdvisingBeanPostProcessor.java:127) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:434) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1885) ~[boxedhello-app.exe:6.0.7]
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:105) ~[boxedhello-app.exe:6.0.7]
        ... 32 common frames omitted

Sample Here is my API:

package nl.cqit.function.poc.java.helloworld.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import nl.cqit.function.poc.java.helloworld.api.model.Person;
import nl.cqit.function.poc.java.helloworld.api.model.Problem;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;

@Validated
@Tag(
    name = "HelloWorld",
    description = "the HelloWorld API"
)
public interface HelloWorldApi {
    @Operation(
        operationId = "sayHello",
        summary = "The main function",
        description = "This endpoint will call sayHello",
        tags = {"HelloWorld"},
        responses = {@ApiResponse(
    responseCode = "200",
    description = "OK",
    content = {@Content(
    mediaType = "application/json",
    schema = @Schema(
    implementation = String.class
)
), @Content(
    mediaType = "application/problem+json",
    schema = @Schema(
    implementation = String.class
)
)}
), @ApiResponse(
    responseCode = "400",
    description = "input validation failed",
    content = {@Content(
    mediaType = "application/json",
    schema = @Schema(
    implementation = Problem.class
)
), @Content(
    mediaType = "application/problem+json",
    schema = @Schema(
    implementation = Problem.class
)
)}
)}
    )
    @RequestMapping(
        method = {RequestMethod.POST},
        value = {"/helloWorld"},
        produces = {"application/json", "application/problem+json"},
        consumes = {"application/json"}
    )
    @ResponseStatus(HttpStatus.OK)
    String sayHello(@Parameter(name = "Person",description = "",required = true) @RequestBody @Valid Person var1);
}

And here is my Feign client:

package nl.cqit.function.poc.java.helloworld.controller;

import nl.cqit.function.poc.java.helloworld.api.HelloWorldApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(name = "helloWorldClient", url = "http://localhost:8080")
public interface HelloWorldClient extends HelloWorldApi {
}

I’m using it like this:

package nl.cqit.function.poc.java.boxedhello.impl;

import nl.cqit.function.poc.java.boxedhello.api.BoxedHelloApi;
import nl.cqit.function.poc.java.boxedhello.api.model.Person;
import nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class BoxedHelloImpl implements BoxedHelloApi {

    @Autowired
    private HelloWorldClient helloWorldClient;

    @Override
    public String sayHello(Person person) {
        String greeting = helloWorldClient.sayHello(map(person));
        String horizontalEdge = "+" + "-".repeat(greeting.length() + 2) + "+";
        return horizontalEdge + "\n| " + greeting + " |\n" + horizontalEdge;
    }

    private nl.cqit.function.poc.java.helloworld.api.model.Person map(Person person) {
        return new nl.cqit.function.poc.java.helloworld.api.model
                .Person(person.getFirstName())
                .lastName(person.getLastName());
    }
}

With this as my Main class:

package nl.cqit.function.poc.java.boxedhello;

import nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients(basePackageClasses = {HelloWorldClient.class})
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

Using this profile for native compilation in my pom.xml:

<profiles>
        <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <image>
                                <builder>paketobuildpacks/builder:tiny</builder>
                                <env>
                                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                                </env>
                            </image>
                        </configuration>
                        <executions>
                            <execution>
                                <id>process-aot</id>
                                <goals>
                                    <goal>process-aot</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.graalvm.buildtools</groupId>
                        <artifactId>native-maven-plugin</artifactId>
                        <configuration>
                            <classesDirectory>${project.build.outputDirectory}</classesDirectory>
                            <metadataRepository>
                                <enabled>true</enabled>
                            </metadataRepository>
                            <requiredVersion>22.3</requiredVersion>
                        </configuration>
                        <executions>
                            <execution>
                                <id>build-native</id>
                                <goals>
                                    <goal>compile-no-fork</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                            <execution>
                                <id>add-reachability-metadata</id>
                                <goals>
                                    <goal>add-reachability-metadata</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

Is this a bug or am I missing something?

About this issue

Most upvoted comments

Hi @korkutkose, it’s high on the priority list. Should be able to handle it before we next release.

Hello @Rocker93, sorry for the delay - I’m going to work on it once we’ve finalised the 2023 release this week.

Thanks @Rocker93 - will check that.

Hi @CC007. Thanks for the update. You’re right. I thought you might have misconfigured Graal plugin (you don’t need to use the Boot parent, but then you need to make sure any build plugins, including Graal are configured correctly and updated if necessary when switching to a newer Boot version).

The problem seems to be that while we register any @FeignClient-annotated interfaces for reflection an proxying, we don’t do it with any supertypes. I’ll work on fixing it.

In the meantime, as a workaround, you can add @RegisterReflectionForBinding(BoxedHelloApi.class) over nl.cqit.function.poc.java.boxedhello.Main to make it execute correctly.

They usually go hand in hand, unless there’s a security patch. So it would be this one: https://github.com/spring-cloud/spring-cloud-release/milestone/137. If we get to the bug before it, we can release an OF only patch, but the Spring Cloud Train milestone is where it’s scheduled.

Any progress on this? We are having the same issue on one of our apps with boot 3.0.7, spring-cloud 2022.0.3 and native-maven-plugin 0.9.23.

Thanks a lot, @CC007. Should be able to get to it around 8th of May.

@OlgaMaciaszek I made my repo public for now. You can find it here: https://github.com/CodeQualIT/CQITFunctions

This issue started occurring after upgrading to Spring Boot 3.0.6 from 3.0.5 so it seems to be some regression bug. Unfortunately I have not been able to track down what causes it.