quarkusio/quarkus · error · IllegalArgumentException

Query annotations may only use interfaces to map results to

Error message

Query annotations may only use interfaces to map results to non-entity types. Offending query string is "${query}" on method ${method} of Repository ${repository}

What it means

The Quarkus Spring Data JPA extension only supports mapping custom @Query results to non-entity types when the return type is an interface, for which it generates an implementation at build time. If the method returns a non-entity class (e.g. a POJO/DTO class) with a custom query, the extension cannot map columns to its fields, so it throws this error during augmentation.

Source

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

                                || getFieldTypeNames(entityClassInfo, entityFieldTypeNames).contains(customResultTypeName)) {
                            // no special handling needed
                            customResultTypeName = null;
                        } else {
                            // The result is using a custom type.
                            List<String> fieldNames = getFieldNames(finalQueryString);

                            // 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 = customResultTypeNames.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 HashMap<>()).put(methodName, fieldNames);
                            } else {
                                throw new IllegalArgumentException(
                                        "Query annotations may only use interfaces to map results to non-entity types. "
                                                + "Offending query string is \"" + finalQueryString + "\" on method "
                                                + methodName
                                                + " of Repository " + repositoryName);
                            }
                        }

                        Expr panacheQuery;
                        if (!finalNamedParameterToIndex.isEmpty()) {
                            Expr parameters = generateParametersObject(finalNamedParameterToIndex, bc, params);

                            // call JpaOperations.find()
                            panacheQuery = bc.invokeStatic(
                                    MethodDesc.of(AdditionalJpaOperations.class, "find",
                                            PanacheQuery.class, AbstractManagedJpaOperations.class, Class.class, String.class,
                                            String.class, io.quarkus.panache.common.Sort.class, Parameters.class),
                                    ops, entityClass,
                                    Const.of(finalQueryString), Const.of(countQueryString),

View on GitHub (pinned to e1c734241f)

Solutions

  1. Convert the projection class to an interface with getter methods matching the selected columns
  2. Use constructor expression in JPQL (SELECT new com.example.Dto(e.a, e.b)) and keep the class return type
  3. Make the returned type a managed @Entity if it should be an entity projection
  4. Split the query so the repository returns entities and map to DTOs in application code

Example fix

// before
class UserDto { private String name; public String getName() {...} }
@Query("SELECT u.name FROM User u")
List<UserDto> findNames();

// after
interface UserDto { String getName(); }
@Query("SELECT u.name FROM User u")
List<UserDto> findNames();
Defensive patterns

Strategy: validation

Validate before calling

// projection return types of @Query methods must be interfaces (or entities)
boolean ok = returnType.isInterface();

Prevention

When it happens

Trigger: A repository method annotated with @Query whose declared return type is a concrete (non-interface, non-entity) class, encountered in CustomQueryMethodsAdder.add while resolving custom result types.

Common situations: Returning a DTO class from a custom JPQL query; migrating from Spring where class-based DTO projection with a constructor worked; defining a projection class instead of an interface.

Related errors


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