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: #3125
sdc-3125-repro-boot4.zip
sdc-3125-repro-boot35.zip
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 leafrepository 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):Leaf WITHOUT redeclaration -> BUG:
Leaf WITH redeclaration -> WORKS:
Observing it (assign to Object so no synthetic checkcast masks the real type):
Actual vs expected:
VERSIONS
HOW TO RUN
Related issue: #3125
sdc-3125-repro-boot4.zip
sdc-3125-repro-boot35.zip