Repository navigation
[WP6] Starter auto-configuration robustness (fluent null-spec, BPP early-init, ordering, API parity) #178
Copy link
Copy link
Closed as not planned
Closed as not planned
[WP6] Starter auto-configuration robustness (fluent null-spec, BPP early-init, ordering, API parity)#178
Bug
Copy link
Labels
status: ready-for-agentFully specified and ready for an autonomous AFK agent to implementFully specified and ready for an autonomous AFK agent to implementtype: bugSomething isn't workingSomething isn't working
Description
Activity
- added a parent issue
on Jul 16, 2026 - addedtype: bugSomething isn't workingSomething isn't workingstatus: ready-for-agentFully specified and ready for an autonomous AFK agent to implementFully specified and ready for an autonomous AFK agent to implementand removed
on Jul 16, 2026 This was generated by AI during triage.
Agent Brief
Category: bug
Summary: Harden the starter auto-configuration and fluent query API: make the fluent
findBySpectolerate empty/null criteria like its siblings (COR-13), stop theBeanPostProcessor@Beanfrom force-instantiating allRepositoryFactoryCustomizerbeans during BPP registration (COR-12), and make auto-configuration ordering contractual rather than reliant on alphabetical FQCN sorting (MAINT-02).Current behavior
- COR-13 (fluent null-spec).
QueryBySpecExecutorAdapter.findBySpec(Object, Function)(theFluentQueryvariant) delegates straight toJpaSpecificationExecutor.findBy(mapper.toSpec(spec, domainClass), queryFunction).SpecMapper.toSpecreturnsnullboth when the criteria object isnulland when no field yields a spec (all-empty criteria). Spring Data JPA'sSimpleJpaRepository.findBy(and its internaldoFindBy) assert the specification is non-null, so an empty/null criteria object throwsIllegalArgumentException("Specification must not be null"). Every sibling method —findBySpec(spec)(List),findBySpec(spec, Sort),findBySpec(spec, Pageable),countBySpec,existsBySpec,findOneBySpec— routes throughfindAll/count/exists/findOne, which tolerate a null specification and return all rows. The existing testQueryBySpecExecutorTest.findByEmptySpecproves empty criteria are a supported use case for the List variant, but there is no equivalent test for the fluent variant. - COR-12 (BPP early-init). In
SpecMapperAutoConfiguration.RepositoryFactoryCustomizerAutoConfiguration, the@Beanfactory method that producesJpaRepositoryFactoryBeanPostProcessoris a non-static instance method whose parameter isList<RepositoryFactoryCustomizer> customizers. Because the returned bean is itself aBeanPostProcessor, Spring must instantiate it during theregisterBeanPostProcessorsphase; resolving theListparameter eagerly instantiates everyRepositoryFactoryCustomizerbean (including user-contributed ones) at that early point, before all regularBeanPostProcessors are registered. Being non-static, the method also forces early creation of the enclosing configuration class. User-defined customizers that inject AOP-proxied /@Transactionalcollaborators can therefore receive un-proxied instances. - MAINT-02 (ordering).
SpecMapperAutoConfigurationis the sole entry inMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, so Spring Boot treats it as an auto-configuration, yet it is annotated plain@Configuration(proxyBeanMethods = false)with no@AutoConfigurationand no ordering relative toJpaRepositoriesAutoConfiguration. Its nestedRepositoryFactoryCustomizerAutoConfigurationgates on@ConditionalOnBean(JpaRepositoryFactoryBean.class), whose reliability depends on being evaluated after the config that registers those beans. It works today only because the FQCN sorts alphabetically afterorg.springframework...JpaRepositoriesAutoConfiguration.
Desired behavior
- COR-13. The fluent
findBySpec(Object, Function)returns results (all rows, subject to the fluent query) for empty/null criteria instead of throwing, matching the behavior and contract of every sibling*BySpecmethod. When mapping yields no specification, substitute an unrestricted specification (a predicate that adds no restriction) before callingfindBy. - COR-12. Registering the starter's
BeanPostProcessorno longer forces eager instantiation ofRepositoryFactoryCustomizerbeans or the enclosing configuration class during the BPP registration phase. Customizer beans are resolved lazily — at the point a repository factory is actually post-processed — so user-defined customizers and their AOP-proxied dependencies are created after allBeanPostProcessors are in place. - MAINT-02. Auto-configuration ordering is contractual:
SpecMapperAutoConfigurationis declared as a proper auto-configuration that is ordered afterJpaRepositoriesAutoConfiguration, so the@ConditionalOnBean(JpaRepositoryFactoryBean.class)gate is evaluated after JPA repository factory beans are registered regardless of FQCN alphabetical order.
Key interfaces
tw.com.softleader.data.jpa.spec.starter.repository.support.QueryBySpecExecutorAdapter#findBySpec(Object, Function)— the fluent variant to fix.tw.com.softleader.data.jpa.spec.SpecCodec#trySpec(Object, Class)/SpecMapper.toSpec(...)— mapping that may yield null.org.springframework.data.jpa.repository.JpaSpecificationExecutor#findBy(Specification, Function)— the delegate that rejects null specs.tw.com.softleader.data.jpa.spec.starter.autoconfigure.SpecMapperAutoConfigurationand its nestedRepositoryFactoryCustomizerAutoConfiguration.tw.com.softleader.data.jpa.spec.starter.repository.support.JpaRepositoryFactoryBeanPostProcessor— constructor currently takesList<RepositoryFactoryCustomizer>.org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration— the auto-configuration to order after.
Acceptance criteria
- Calling the fluent
findBySpec(Object, Function)with an empty criteria object (all fields null) returns the fluent result over all rows rather than throwingIllegalArgumentException, and withnullcriteria behaves identically to the other*BySpecmethods givennull. - A new test mirroring
QueryBySpecExecutorTest.findByEmptySpeccovers the fluent variant (e.g.findBy(q -> q.all())or a projection) with empty criteria and asserts all rows are returned. - The
@Beanmethod producingJpaRepositoryFactoryBeanPostProcessorno longer eagerly resolves aList<RepositoryFactoryCustomizer>; customizers are supplied lazily (e.g. viaObjectProvider<RepositoryFactoryCustomizer>resolved insidepostProcessBeforeInitialization), and the method is declaredstaticso the enclosing configuration class is not force-created during BPP registration. -
JpaRepositoryFactoryBeanPostProcessorstill applies every registeredRepositoryFactoryCustomizerto eachJpaRepositoryFactoryBeanit post-processes (existing customizer application behavior is preserved). -
SpecMapperAutoConfigurationis declared as a proper auto-configuration ordered afterJpaRepositoriesAutoConfiguration(e.g.@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)), so the nested@ConditionalOnBean(JpaRepositoryFactoryBean.class)gate no longer depends on alphabetical FQCN ordering. - The existing starter test suite (repository wiring, SpecMapper injection,
findBySpec/countBySpec/existsBySpecbehavior, customizer/resolver customization tests) continues to pass unchanged.
Out of scope
- MAINT-05 (API parity —
findBySpec(spec, countSpec, Pageable)anddeleteBySpec(spec)). Deliberately excluded from this agent brief. Adding methods to the publicQueryBySpecExecutorinterface is a public-API-surface decision, and the issue itself flags that the absence ofdelete(spec)"may be deliberate." Whether to expose criteria-driven bulk deletion and a cheaper-count paged overload — and their exact signatures — needs a maintainer decision. Track separately. - No change to the mapping semantics of
SpecMapper.toSpec(it may continue to returnnull); the fix lives at the adapter/delegation layer. - No change to the public method signatures of existing
QueryBySpecExecutormethods.
- COR-13 (fluent null-spec).
Metadata
Metadata
Assignees
Labels
status: ready-for-agentFully specified and ready for an autonomous AFK agent to implementFully specified and ready for an autonomous AFK agent to implementtype: bugSomething isn't workingSomething isn't working
Work package WP6 · findings: COR-13, COR-12, MAINT-02, MAINT-05 · worst severity: medium
From the 2026-07-16 whole-codebase review (branch
jakarta, modulesmapper/+starter/). Each finding below is CONFIRMED by independent adversarial verification. File paths and line numbers reflect the review snapshot and may drift — treat them as starting points, not exact coordinates.COR-13 — Fluent findBySpec throws IllegalArgumentException for null or empty criteria, unlike every sibling method
starter/src/main/java/tw/com/softleader/data/jpa/spec/starter/repository/support/QueryBySpecExecutorAdapter.java:116findBy(mapper.toSpec(spec, domainClass), queryFunction). SpecMapper.toSpec (mapper/src/main/java/tw/com/softleader/data/jpa/spec/SpecMapper.java:104-114) returns null both when rootObject is null and when no field produces a spec (all-empty criteria). SimpleJpaRepository.findBy in Spring Data JPA 3.5.12 (the version resolved by Boot BOM 3.5.15) executesAssert.notNull(spec, "Specification must not be null"), whereas findAll/findOne/count/exists tolerate a null Specification — the project's own QueryBySpecExecutorTest.findByEmptySpec proves empty criteria are a supported use case for the list variant.var s = mapper.trySpec(spec, domainClass).orElse((root, query, cb) -> null); return findBy(s, queryFunction);, and add a test mirroring findByEmptySpec for the fluent variant.COR-12 — BeanPostProcessor declared as non-static @bean forces early instantiation of all RepositoryFactoryCustomizer beans and their dependencies
starter/src/main/java/tw/com/softleader/data/jpa/spec/starter/autoconfigure/SpecMapperAutoConfiguration.java:116List<RepositoryFactoryCustomizer> customizersmakes the container instantiate every RepositoryFactoryCustomizer bean — including user-defined ones — during the BeanPostProcessor registration phase, before other BeanPostProcessors are in place. The starter's own two customizers defer their dependencies via ObjectProvider, but user-contributed customizer beans (which SpecMapperAutoConfiguration explicitly supports) receive no such protection, and the non-static method additionally forces early creation of the enclosing configuration class.ObjectProvider<RepositoryFactoryCustomizer>(resolved lazily inside postProcessBeforeInitialization) instead of a List parameter, so customizer beans are created on first repository factory initialization rather than at BPP registration.MAINT-02 — Auto-configuration lacks @AutoConfiguration/@AutoConfigureAfter; @ConditionalOnBean(JpaRepositoryFactoryBean) correctness rests on alphabetical ordering
starter/src/main/java/tw/com/softleader/data/jpa/spec/starter/autoconfigure/SpecMapperAutoConfiguration.java:111MAINT-05 — QueryBySpecExecutor lacks counterparts for JpaSpecificationExecutor's findAll(spec, countSpec, pageable) and delete(spec)
starter/src/main/java/tw/com/softleader/data/jpa/spec/starter/repository/QueryBySpecExecutor.java:44Full report: see the
codebase-review-2026-07-16branch (linked on the parent tracking issue).