quarkusio/quarkus · error · IllegalArgumentException

Sub resource type is not a class: " + returnType.name().toSt

Error message

Sub resource type is not a class: " + returnType.name().toString()

What it means

When a REST client interface method has no HTTP method annotation, Quarkus treats it as a sub-resource method and inspects its return type. If that return type is neither a class nor a parameterized type (e.g. a primitive, array, or type variable), there is no interface to generate a sub-resource locator against, so generation fails.

Source

Thrown at extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java:1608

        }

        return result;
    }

    private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem> enrichers,
            BuildProducer<GeneratedClassBuildItem> generatedClasses, ClassInfo interfaceClass, IndexView index,
            String defaultMediaType, Map<DotName, String> httpAnnotationToMethod, String name,
            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<>();

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add the appropriate HTTP method annotation (@GET/@POST/...) to the client method so it is not treated as a sub-resource method
  2. If it is a sub-resource method, change the return type to an interface (or parameterized interface)
  3. Remove the offending method if it is not meant to be a client endpoint

Example fix

// before
@Path("items")
int getItemsCount();
// after
@Path("items")
@GET
long getItemsCount();
Defensive patterns

Strategy: validation

Validate before calling

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

static void checkSubResourceReturnType(Class<?> iface) {
    for (Method m : iface.getMethods()) {
        if (m.isAnnotationPresent(jakarta.ws.rs.GET.class)
            || m.isAnnotationPresent(jakarta.ws.rs.POST.class)
            || m.isAnnotationPresent(jakarta.ws.rs.PUT.class)
            || m.isAnnotationPresent(jakarta.ws.rs.DELETE.class)
            || m.isAnnotationPresent(jakarta.ws.rs.PATCH.class)
            || m.isAnnotationPresent(jakarta.ws.rs.HEAD.class)
            || m.isAnnotationPresent(jakarta.ws.rs.OPTIONS.class)) continue;
        Class<?> r = m.getReturnType();
        if (!(r.isInterface())) {
            throw new IllegalStateException(
                m + " has no HTTP annotation and returns non-interface " + r);
        }
    }
}

Type guard

static boolean isHttpAnnotatedOrInterfaceReturn(Method m) {
    boolean http = m.getAnnotations().length > 0 && java.util.Arrays.stream(m.getAnnotations())
        .anyMatch(a -> a.annotationType().getName().startsWith("jakarta.ws.rs."));
    return http || m.getReturnType().isInterface();
}

Prevention

When it happens

Trigger: A client interface method without @GET/@POST/etc. whose return type is a primitive, array, type variable, or other non-class Type kind — e.g. `int getResource();` or `T[] items();` with no HTTP annotation.

Common situations: Typos where the HTTP annotation import is missing so the method silently becomes a sub-resource locator; generic type-variable returns erased/ unresolved; Kotlin or generated interfaces exposing non-class returns on un-annotated methods.

Related errors


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