Skip to content

Resolve repository method return types against the repository interface#3512

Open
Develop-KIM wants to merge 1 commit into
spring-projects:mainfrom
Develop-KIM:issue/3507
Open

Resolve repository method return types against the repository interface#3512
Develop-KIM wants to merge 1 commit into
spring-projects:mainfrom
Develop-KIM:issue/3507

Conversation

@Develop-KIM

Copy link
Copy Markdown
  • You have read the Spring Data contribution guidelines.
  • You use the code formatters provided here and have them applied to your changes. Don't submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.
  • You added yourself as author in the headers of the classes you touched.

A generic @NoRepositoryBean base declaring T findById(ID id) hands back an Optional<T> at runtime instead of T.

QueryExecutorMethodInterceptor.invoke(…) builds the return type as new MethodParameter(method, -1) with no containing class, so T resolves against Repository<T, ID> and ends up as Object. QueryExecutionResultHandler.processingRequired(…) then asks !targetType.isInstance(source), which is false for a target type of Object, and the Optional goes back to the caller untouched.

This registers the repository interface as containing class of that MethodParameter, the same thing MethodLookups already does for parameter types.

The test added for #3125 didn't catch this because it calls .withContainingClass(…) on the MethodParameter itself, which the production call site never did, so it was green against a code path that doesn't exist. I put the new test on the proxy instead — it fails on current main with ClassCastException: class java.util.Optional cannot be cast to class …sample.User and passes with the change. Full suite green (3856 tests).

Written with AI assistance (Claude Code); the commit carries the co-author trailer.

Query execution resolved the return type of a repository method from the method declaration alone. A return type declared as a type variable on a base interface, such as `T findById(ID id)` on a generic `@NoRepositoryBean` base, therefore resolved to the type variable's bound rather than the concrete domain type. Result post-processing then saw a target type of `Object`, concluded that no processing was required and returned the store's `Optional` unchanged, so callers observed an `Optional` where the method declared `T`.

We now register the repository interface as containing class of the return type's `MethodParameter` so that the type variable resolves against the concrete repository interface, following what `MethodLookups` already does for parameter types.

The regression test added for spring-projects#3125 passed because it constructed the `MethodParameter` with the containing class itself, which the production call site never did. The test added here covers the proxy instead.

Closes spring-projects#3507
Related tickets spring-projects#3125

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Develop-KIM <kimdonghwan913@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants