Custom Repository Implementation in Spring Data JPA
Fast recall
Quick revision
Core answer
Whether you know the fragment/Impl-suffix pattern for logic beyond derived methods and @Query
Breaking the exact Impl naming convention or trying to add a method body to the repository interface itself
Mention the Impl suffix is configurable (repositoryImplementationPostfix) and that the Criteria API fits dynamic, optional-filter searches
Interview-ready guide
Interview answer
For example, OrderSearch defines search(OrderFilter filter), OrderSearchImpl implements it with EntityManager, and OrderRepository extends JpaRepository<Order, Long>, OrderSearch. The default postfix makes OrderSearchImpl the fragment implementation. If repositoryImplementationPostfix is changed, the class name must follow that configuration instead of a universal hard-coded Impl rule.
Custom Repository Implementation in Spring Data JPA
Direct Answer
Add custom repository behavior with a fragment interface and an implementation discovered by the configured repository-implementation postfix, which defaults to Impl. The main repository extends the fragment interface, and Spring composes the generated repository with that fragment. The recommended fragment model is different from the older pattern of naming one custom class after the whole repository interface.
Code Example
// 1) custom interface
public interface OrderRepositoryCustom {
List<Order> findWithFilters(String status, LocalDate from, BigDecimal minAmount);
}
// 2) implementation — note the Impl suffix
public class OrderRepositoryCustomImpl implements OrderRepositoryCustom {
@PersistenceContext private EntityManager em;
@Override
public List<Order> findWithFilters(String status, LocalDate from, BigDecimal minAmount) {
var cb = em.getCriteriaBuilder();
var cq = cb.createQuery(Order.class);
var root = cq.from(Order.class);
var predicates = new ArrayList<Predicate>();
if (status != null) predicates.add(cb.equal(root.get("status"), status));
if (from != null) predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), from.atStartOfDay()));
if (minAmount != null) predicates.add(cb.greaterThan(root.get("amount"), minAmount));
cq.where(predicates.toArray(Predicate[]::new));
return em.createQuery(cq).getResultList();
}
}
// 3) main repository extends both
public interface OrderRepository
extends JpaRepository<Order, Long>, OrderRepositoryCustom { }
Common Mistakes & Pitfalls
- Wrong name — the class must be
<CustomInterface>Implexactly;...Implementationwon't be found. - Adding a body to the repository interface — interfaces can't hold logic; that's what the fragment is for.
- Injecting the main repository into the Impl — risks a circular dependency; use
EntityManager. - Reinventing dynamic queries — for many optional filters, Spring Data Specifications or QueryDSL may be cleaner than hand-written Criteria.
Check your understanding
Answer these without looking back at the article.
- What is the naming convention for custom repository implementation classes?
- When would you use EntityManager directly over @Query?
- What is Querydsl and how does it integrate with Spring Data?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.