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

  1. If it's a sub-resource method, change the return type to an interface
  2. If it's an endpoint, add an HTTP method annotation (@GET, @POST, etc.)
  3. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/5cf5bd3f8aff99ae. Report an issue: GitHub.