spring-projects / spring-projects/spring-framework
Default method invocation fails in native image with `HttpServiceProxyFactory` and `RSocketServiceProxyFactory`
@sbrannen is already working on this.
Since Sep 4, 2026.
- Dominant language
- Java
- Stars
- 60.2k
- Forks
- 38.8k
- Avg merge
- 5d 2h
- Merged PRs (30d)
- 27
Description
Description
A default method declared on an HTTP Service interface works on the JVM but fails at runtime in a GraalVM native image.
HttpServiceProxyFactory recognizes the default method and delegates to InvocationHandler.invokeDefault(...). In the native image, that invocation attempts to reflectively invoke the generated proxy class's private proxyClassLookup(MethodHandles.Lookup) method, which is not registered in the generated reachability metadata.
The native image builds successfully. The error occurs only when the default method is invoked at runtime.
Versions
- Spring Boot: 4.1.0
- Spring Framework: 7.0.8
- GraalVM Native Build Tools: 1.1.9
- Oracle GraalVM: 25.2.4+7.1
- Java: 25.0.4
- Gradle: 9.7
- macOS ARM64
Relevant dependencies:
implementation("org.springframework.boot:spring-boot-starter-webmvc")
implementation("org.springframework.boot:spring-boot-http-client")
Minimal example
package example;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.service.annotation.GetExchange;
public interface TestHttpService {
@GetExchange("/echo")
String exchange(
@RequestHeader(value = "X-Test", required = false)
String optionalHeader);
default String convenience() {
return exchange(null);
}
}
package example;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.registry.ImportHttpServices;
@SpringBootApplication(proxyBeanMethods = false)
@ImportHttpServices(group = "test", types = TestHttpService.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
RestClientHttpServiceGroupConfigurer httpServiceGroups() {
return groups ->
groups.filterByName("test")
.forEachClient(
(group, builder) ->
builder.baseUrl("http://127.0.0.1:8080"));
}
@Bean
ApplicationRunner invokeDefaultMethod(TestHttpService service) {
return args -> System.out.println(service.convenience());
}
@RestController
static class EchoController {
@GetMapping("/echo")
String echo() {
return "ok";
}
}
}
Steps to reproduce
Run on the JVM:
./gradlew bootRun
The default method completes successfully and prints:
ok
Build and run the native image:
./gradlew nativeCompile
./build/native/nativeCompile/demo
The native image builds successfully, but invoking service.convenience() fails.
Actual behavior
The relevant part of the exception is:
org.graalvm.nativeimage.MissingReflectionRegistrationError:
Cannot reflectively invoke method
'private static java.lang.invoke.MethodHandles$Lookup
jdk.proxy4.$Proxy...proxyClassLookup(java.lang.invoke.MethodHandles$Lookup)'
at java.lang.reflect.Proxy.defaultMethodHandle(Proxy.java)
at java.lang.reflect.Proxy.invokeDefault(Proxy.java)
at java.lang.reflect.InvocationHandler.invokeDefault(InvocationHandler.java)
at org.springframework.web.service.invoker.HttpServiceProxyFactory
$HttpServiceMethodInterceptor.invoke(HttpServiceProxyFactory.java)
Spring AOT generates proxy metadata containing the expected interface combination:
{
"type": {
"proxy": [
"example.TestHttpService",
"org.springframework.aop.SpringProxy",
"org.springframework.aop.framework.Advised",
"org.springframework.core.DecoratingProxy"
]
}
}
However, invocation of the generated proxy's proxyClassLookup method is not registered.
Expected Behavior
Default methods supported by HttpServiceProxyFactory should behave consistently on the JVM and in a native image.
Alternatively, if default methods cannot currently be supported in native images, this limitation should be detected during AOT processing or documented.
Workaround
Calling the annotated exchange method directly works:
service.exchange(null);
Removing the convenience default method avoids the InvocationHandler.invokeDefault(...) branch and the native image works correctly.
Registering the generated proxy's private proxyClassLookup method manually does not appear to be a desirable application-level workaround, because the proxy class and its interface composition are framework/JDK implementation details.
Question
Should the HTTP Service proxy AOT processing register the metadata required by InvocationHandler.invokeDefault(...), or is this expected to be handled elsewhere?
Related Issues
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.