quarkusio/quarkus · error · IllegalArgumentException
Client interface method: " + jandexMethod.declaringClass().n
Error message
Client interface method: " + jandexMethod.declaringClass().name() + "#" + jandexMethod + " has no HTTP method annotation (@GET, @POST, etc) and it's return type: " + returnType.name().toString() + " is not an interface. If it's a sub resource method, it has to return an interface. If it's not, it has to have one of the HTTP method annotations.
What it means
A client interface method without an HTTP method annotation is interpreted as a sub-resource method, which must return an interface so Quarkus can generate a client implementation for it. If the return type resolves to a concrete class, generation cannot proceed and this error is thrown.
Source
Thrown at extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java:1616
ClassRestClientContext ownerContext, ResultHandle ownerTarget, int methodIndex,
ResourceMethod method, String[] javaMethodParameters, MethodInfo jandexMethod,
Set<ClassInfo> multipartResponseTypes, List<SubResourceParameter> ownerSubResourceParameters,
Map<GeneratedSubResourceKey, String> generatedSubResources, Map<String, Type> ownerIdentifierTypeLookupMap) {
// resolve type variables of the reurntype, mainly for the generatedSubResources cache
Type returnType = resolveType(jandexMethod.returnType(), ownerIdentifierTypeLookupMap, jandexMethod);
if (returnType.kind() != CLASS && returnType.kind() != PARAMETERIZED_TYPE) {
// sort of sub-resource method that returns a thing that isn't a class
throw new IllegalArgumentException("Sub resource type is not a class: " + returnType.name().toString());
}
Map<DotName, Map<String, Type>> hierarchyIdentifierTypeLookupMap = buildhierarchyIdentifierTypeLookupMap(index, null,
returnType);
ClassInfo subInterface = index.getClassByName(returnType.name());
if (!Modifier.isInterface(subInterface.flags())) {
throw new IllegalArgumentException(
"Client interface method: " + jandexMethod.declaringClass().name() + "#" + jandexMethod
+ " has no HTTP method annotation (@GET, @POST, etc) and it's return type: "
+ returnType.name().toString() + " is not an interface. "
+ "If it's a sub resource method, it has to return an interface. "
+ "If it's not, it has to have one of the HTTP method annotations.");
}
ownerContext.createJavaMethodField(interfaceClass, jandexMethod, methodIndex);
List<SubResourceMethodParameterKeyPart> ownerSubResourceMethodParameters = new ArrayList<>();
for (SubResourceParameter ownerSubResourceParameter : ownerSubResourceParameters) {
ownerSubResourceMethodParameters.add(SubResourceMethodParameterKeyPart.of(ownerSubResourceParameter));
}
// method parameters (except path parameters) are rewritten to sub client fields (directly, public fields):
List<SubResourceMethodParameterKeyPart> subResourceMethodParameters = new ArrayList<>();
MethodParameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {View on GitHub (pinned to e1c734241f)
Solutions
- If it's a sub-resource method, change the return type to an interface
- If it's an endpoint, add an HTTP method annotation (@GET, @POST, etc.)
- Split the class into an interface + implementation and reference the interface in the client
Example fix
// before
@Path("sub")
SubResourceImpl getSubResource();
// after
@Path("sub")
SubResource getSubResource(); // SubResource is an interface Defensive patterns
Strategy: validation
Validate before calling
static void requireInterfaceReturn(Class<?> client, String method) throws Exception {
Method m = client.getMethod(method);
if (!m.getReturnType().isInterface()) {
throw new IllegalStateException(
client.getName() + "#" + method + " must return an interface (sub-resource) or carry an HTTP annotation");
}
} Type guard
static boolean validClientMethod(Method m) {
boolean hasHttp = java.util.Arrays.stream(m.getAnnotations())
.anyMatch(a -> a.annotationType().getName().startsWith("jakarta.ws.rs."));
return hasHttp || m.getReturnType().isInterface();
} Prevention
- Program against interfaces in client APIs — return the interface, not the impl
- Verify every non-annotated method on a @RegisterRestClient is an intentional sub-resource locator
- Document team convention: client interfaces only expose interface-typed sub-resources
When it happens
Trigger: Calling a method like `@Path("sub") MyServiceImpl getSub();` on a @RegisterRestClient interface — no @GET/@POST annotation and a class (not interface) return type.
Common situations: Returning a concrete implementation class instead of the JAX-RS interface; forgetting an HTTP annotation on what was meant to be an endpoint; migrating a server-side resource class to the client side where sub-resources must be interfaces.
Related errors
- Sub resource type is not a class: " + returnType.name().toSt
- 'quarkus-narayana-lra' can only work if 'quarkus-rest-client
- Token exchange is required but OIDC client is configured to
- Not possible to define the scope %s for the REST client %s
- Unable to lookup configuration for REST Client ${restClientI
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/5cf5bd3f8aff99ae.
Report an issue: GitHub.