spring-projects / spring-projects/spring-data-commons

`findById` declared as `T` on a generic repository base returns `Optional<T>`

Open
#3,507 1 comment 0 reactions 1 assignee View on GitHub

@mp911de is already working on this.

Since Aug 3, 2026.

type: bug
Dominant language
Java
Stars
838
Forks
730
PR merge metrics
No merged PRs in 30d

Description

A @NoRepositoryBean base repository interface, parameterized as <T, ID> and extending PagingAndSortingRepository + QueryByExampleExecutor (NOT CrudRepository), declares T findById(ID) to return the unwrapped entity. When a concrete leaf
repository extends it WITHOUT redeclaring findById, the generated proxy returns Optional instead of the declared T. Redeclaring T findById(ID) concretely on the leaf is the only workaround. This is #3125 (milestone "fixed 3.2.9"), but it still reproduces on the latest 3.5.x AND the latest 4.1.x -- so it has regressed or never covered this two-parent base shape.

CLEAN EXAMPLES

Base (declares T findById, no CrudRepository):

    @NoRepositoryBean
    public interface AbstractJpaDAO<T, ID>
            extends QueryByExampleExecutor<T>, PagingAndSortingRepository<T, ID> {
        T findById(ID id);
    }

Leaf WITHOUT redeclaration -> BUG:

    public interface FooDAO extends AbstractJpaDAO<Foo, Long> { }
    // fooDAO.findById(1L)  ->  runtime type java.util.Optional  (declared: Foo)

Leaf WITH redeclaration -> WORKS:

    public interface FooWorkaroundDAO extends AbstractJpaDAO<Foo, Long> {
        @Override Foo findById(Long id);
    }
    // fooWorkaroundDAO.findById(1L)  ->  Foo

Observing it (assign to Object so no synthetic checkcast masks the real type):

    Object result = fooDAO.findById(id);
    assertThat(result).isInstanceOf(Foo.class);   // fails: actual is Optional[Foo]

Actual vs expected:

expected: instance of com.example.repro.Foo
actual  : Optional[com.example.repro.Foo@...]   (java.util.Optional)

VERSIONS

Version     spring-data-commons   Hibernate    leaf w/o redeclare   workaround
---------   -------------------   ---------    ------------------   ----------
Boot 3.5.5  3.5.3                 6.6.26       Optional[Foo]  FAIL   Foo  OK
Boot 4.1.0  4.1.0                 7.4.1        Optional[Foo]  FAIL   Foo  OK

Java 21, H2 (in-memory). Only public Maven Central artifacts required.

HOW TO RUN

unzip the archive, then:  mvn test

Related issue: https://github.com/spring-projects/spring-data-commons/issues/3125

sdc-3125-repro-boot4.zip
sdc-3125-repro-boot35.zip

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.