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
- Add the appropriate HTTP method annotation (@GET/@POST/...) to the client method so it is not treated as a sub-resource method
- If it is a sub-resource method, change the return type to an interface (or parameterized interface)
- 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
- Always annotate endpoint methods with an HTTP method annotation
- Sub-resource methods must return interfaces, never classes or primitives
- Check imports — a missing @GET import silently turns methods into sub-resource locators
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
- Client interface method: " + jandexMethod.declaringClass().n
- '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/581868264b7d1161.
Report an issue: GitHub.