{"record":{"id":"2148944ef768e4a9","repo":"quarkusio/quarkus","slug":"query-annotations-may-only-use-interfaces-to-map-r","errorCode":null,"errorMessage":"Query annotations may only use interfaces to map results to non-entity types. Offending query string is \"${query}\" on method ${method} of Repository ${repository}","messagePattern":"Query annotations may only use interfaces to map results to non-entity types\\. Offending query string is \"(.+?)\" on method (.+?) of Repository (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java","lineNumber":355,"sourceCode":"                                || getFieldTypeNames(entityClassInfo, entityFieldTypeNames).contains(customResultTypeName)) {\n                            // no special handling needed\n                            customResultTypeName = null;\n                        } else {\n                            // The result is using a custom type.\n                            List<String> fieldNames = getFieldNames(finalQueryString);\n\n                            // If the custom type is an interface, we need to generate the implementation\n                            ClassInfo resultClassInfo = index.getClassByName(customResultTypeName);\n                            if (Modifier.isInterface(resultClassInfo.flags())) {\n                                // Find the implementation name, and use that for subsequent query result generation\n                                customResultTypeName = customResultTypeNames.computeIfAbsent(customResultTypeName,\n                                        (k) -> createSimpleInterfaceImpl(k, entityClassInfo.name()));\n\n                                // Remember the parameters for this usage of the custom type, we'll deal with it later\n                                customResultTypes.computeIfAbsent(customResultTypeName,\n                                        k -> new HashMap<>()).put(methodName, fieldNames);\n                            } else {\n                                throw new IllegalArgumentException(\n                                        \"Query annotations may only use interfaces to map results to non-entity types. \"\n                                                + \"Offending query string is \\\"\" + finalQueryString + \"\\\" on method \"\n                                                + methodName\n                                                + \" of Repository \" + repositoryName);\n                            }\n                        }\n\n                        Expr panacheQuery;\n                        if (!finalNamedParameterToIndex.isEmpty()) {\n                            Expr parameters = generateParametersObject(finalNamedParameterToIndex, bc, params);\n\n                            // call JpaOperations.find()\n                            panacheQuery = bc.invokeStatic(\n                                    MethodDesc.of(AdditionalJpaOperations.class, \"find\",\n                                            PanacheQuery.class, AbstractManagedJpaOperations.class, Class.class, String.class,\n                                            String.class, io.quarkus.panache.common.Sort.class, Parameters.class),\n                                    ops, entityClass,\n                                    Const.of(finalQueryString), Const.of(countQueryString),","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java#L337-L373","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert the projection class to an interface with getter methods matching the selected columns","Use constructor expression in JPQL (SELECT new com.example.Dto(e.a, e.b)) and keep the class return type","Make the returned type a managed @Entity if it should be an entity projection","Split the query so the repository returns entities and map to DTOs in application code"],"exampleFix":"// before\nclass UserDto { private String name; public String getName() {...} }\n@Query(\"SELECT u.name FROM User u\")\nList<UserDto> findNames();\n\n// after\ninterface UserDto { String getName(); }\n@Query(\"SELECT u.name FROM User u\")\nList<UserDto> findNames();","handlingStrategy":"validation","validationCode":"// projection return types of @Query methods must be interfaces (or entities)\nboolean ok = returnType.isInterface();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always model projections as interfaces","Use SELECT new ... constructor expressions for class DTOs","Keep entity vs DTO return types documented per repository"],"tags":["quarkus","spring-data-jpa","build-time","dto-projection","custom-query"],"backgroundTag":"unsupported-query-result-type","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}