quarkusio/quarkus · error · IllegalArgumentException

${method} of Repository ${repository} can only use interface

Error message

${method} of Repository ${repository} can only use interfaces to map results to non-entity types.

What it means

When a derived query's return type is not the entity type, Quarkus's spring-data-jpa extension maps results into that type at build time by generating an implementation — but only if the type is an interface (an interface projection backed by entity getters). Returning a concrete non-entity class cannot be supported by generated bytecode, so the build fails with this IllegalArgumentException.

Source

Thrown at extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java:276

                            customResultTypeName = resultType.name();

                            if (customResultTypeName.equals(entityClassInfo.name())
                                    || isHibernateSupportedReturnType(customResultTypeName)) {
                                // no special handling needed
                                customResultTypeName = null;
                            } else {
                                // If the custom type is an interface, we need to generate the implementation
                                ClassInfo resultClassInfo = index.getClassByName(customResultTypeName);
                                if (Modifier.isInterface(resultClassInfo.flags())) {
                                    // Find the implementation name, and use that for subsequent query result generation
                                    customResultTypeName = customResultTypeImplNames.computeIfAbsent(customResultTypeName,
                                            k -> createSimpleInterfaceImpl(k, entityClassInfo.name()));

                                    // Remember the parameters for this usage of the custom type, we'll deal with it later
                                    customResultTypes.computeIfAbsent(customResultTypeName,
                                            k -> new ArrayList<>()).add(method.name());
                                } else {
                                    throw new IllegalArgumentException(
                                            method.name() + " of Repository " + repositoryClassInfo
                                                    + " can only use interfaces to map results to non-entity types.");
                                }
                            }
                        }

                        DotName effectiveReturnTypeName = finalReturnType.kind() == Type.Kind.TYPE_VARIABLE ? DotNames.OBJECT
                                : finalReturnType.name();

                        generateFindQueryResultHandling(bc, panacheQuery, finalPageableParameterIndex, params,
                                repositoryClassInfo, entityClassInfo, effectiveReturnTypeName, parseResult.getTopCount(),
                                method.name(), customResultTypeName,
                                entityClassInfo.name().toString(), elementTypeToCast);

                    } else if (parseResult.getQueryType() == MethodNameParser.QueryType.COUNT) {
                        if (!DotNames.PRIMITIVE_LONG.equals(returnType.name())
                                && !DotNames.LONG.equals(returnType.name())) {
                            throw new IllegalArgumentException(

View on GitHub (pinned to e1c734241f)

Solutions

  1. Convert the DTO class to an interface with getters matching the entity's properties (interface projection)
  2. Use the entity type as the return type and map to a DTO manually in application code
  3. Use a @Query with a constructor/SELECT projection if a concrete class is really required

Example fix

// before
class PersonDto { private String name; public String getName() { return name; } }
PersonDto findByEmail(String email);

// after
interface PersonNameView { String getName(); }
PersonNameView findByEmail(String email);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure custom result types used in repository methods are interfaces
static void checkProjectionTypes(Class<?> repo) {
    for (Method m : repo.getDeclaredMethods()) {
        Class<?> r = m.getReturnType();
        if (!r.isInterface() && !r.isAnnotationPresent(Entity.class) && !isBuiltIn(r))
            throw new IllegalStateException(m.getName() + " returns non-interface non-entity " + r);
    }
}

Prevention

When it happens

Trigger: Declaring e.g. PersonDto findByEmail(String email) where PersonDto is a concrete class (not the entity Person and not an interface), inside a method whose query type is SELECT and whose custom result type is not an interface.

Common situations: Creating a DTO class with fields copied from the entity and using it directly as a return type; migrating from another framework (e.g. JPA constructor-expression queries) where class-based projections are allowed.

Related errors


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