skip to content

Compare default methods on a repository interface with fragment implementations. When do you choose each, and how do they interact with the base implementation and derived queries?

level: principalimportance: nice to knowfreq 18%

answer

  1. Default = inline, no Impl, no DI, composition only
  2. Fragment = class, constructor DI, can override CRUD
  3. Both suppress query derivation
  4. Reach for EntityManager -> use a fragment
  5. Don't duplicate a signature as both

basics

~20 s

Default methods live on the interface and are run directly by the proxy — good for simple logic that just composes existing repository methods, with no Impl class. Fragments are separate classes for real custom logic needing injected collaborators (EntityManager, external clients) and can override CRUD methods.

solid answer

~50 s

Both add custom behavior without a generated query, but they differ in mechanism and fit. A default method is written inline on an interface the repository extends; the proxy invokes it directly via a method handle, so it has no separate Impl and can't easily take injected dependencies — its natural use is lightweight composition, calling other repository methods (e.g., a convenience `findActiveByEmail` that calls `findByEmailAndActiveTrue`). A fragment is a full class with its own state and constructor injection, so it's the right tool when you need `EntityManager`, a `CriteriaBuilder`, an external client, or transactional imperative logic, and it participates in the composition with defined precedence over the base — letting it override `save`/`delete`. Both suppress name-based query derivation for the methods they provide. Rule of thumb: default method for trivial delegation, fragment for real logic or dependencies.

code

java · 24 lines
java
// Default method: thin composition, no Impl, no injected deps
public interface OrderRepository extends JpaRepository<Order, Long>, OrderReportingFragment {
    List<Order> findByStatus(OrderStatus status);

    default List<Order> findOpenOrders() {          // default method
        return findByStatus(OrderStatus.OPEN);      // just composes an existing method
    }
}

// Fragment: needs EntityManager -> must be a class with injection
public interface OrderReportingFragment {
    BigDecimal revenueBetween(LocalDate from, LocalDate to);
}
public class OrderReportingFragmentImpl implements OrderReportingFragment {
    private final EntityManager em;
    public OrderReportingFragmentImpl(EntityManager em) { this.em = em; }
    public BigDecimal revenueBetween(LocalDate from, LocalDate to) {
        return em.createQuery(
            "select coalesce(sum(o.total),0) from Order o " +
            "where o.placedOn between :from and :to", BigDecimal.class)
            .setParameter("from", from).setParameter("to", to)
            .getSingleResult();
    }
}

go deeper

for a junior

Know default methods live on the interface; fragments are separate classes.

for a middle

Explain that default methods can't inject deps and suit simple composition.

for a senior

Contrast precedence, CRUD overriding, and derivation suppression for both.

for a principal

Set policy: thin default methods vs fragments for real logic, prefer auditing/AOP over CRUD overrides, govern cross-cutting shared fragments.

## Two extension mechanisms, same goal Both default methods and fragments let you put **hand-written behavior** on a repository so the framework doesn't try to derive a query for that method. They differ in *how* and *what they can do*. ### Default methods - Declared **inline** on an interface the repository extends (often the repository interface itself or a fragment interface). - The runtime proxy detects the method is `default` and **invokes it directly** through a Java method handle — no `Impl` class, no composition entry of its own. - **No constructor injection**: a default method has only `this` (the repository proxy) to work with, so its idiomatic use is **composition** — calling other repository methods and combining/transforming results. - Great for: convenience overloads, null-safe wrappers, small transformations, defaulting parameters. - Because it is code you wrote, **query derivation is skipped** for it. ```java interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); default User getByEmailOrThrow(String email) { // default method return findByEmail(email) .orElseThrow(() -> new UserNotFoundException(email)); } } ``` ### Fragments - A **separate interface + Impl class** with real state and **constructor-injected collaborators** (`EntityManager`, `JdbcTemplate`, an HTTP client...). - Participate in the repository's **`RepositoryComposition`** with a defined order, so they can **override base CRUD methods** (`save`, `delete`) — something a default method's mechanism isn't designed for. - Right tool for: Criteria/QueryDSL queries, bulk operations, integrating non-JPA systems, cross-cutting persistence logic. ## Interaction summary | Aspect | Default method | Fragment | |---|---|---| | Where it lives | inline on interface | separate `Impl` class | | Dependency injection | no (only `this`) | yes (constructor) | | Overrides base CRUD | not its purpose | yes, via composition order | | Suppresses query derivation | yes | yes | | Testability | via proxy/integration | plain unit test of Impl | ## Precedence subtleties Methods **provided** by a default method or a fragment are not derived as queries. Between mechanisms, fragments have explicit ordering in the composition; default methods are invoked directly when the call lands on that interface method. For deliberately overriding a base method, prefer a **fragment** because its precedence over `SimpleJpaRepository` is well defined. Avoid declaring the *same* signature as both a default method and a fragment — it muddies which runs and confuses readers; pick one mechanism per method. ## Design guidance (principal lens) - Keep default methods **thin and dependency-free**; the moment you reach for `EntityManager` or an external service, promote to a fragment. - Use **shared fragments** for cross-cutting behavior (soft-delete, audit) reused by many repositories; use **default methods** for per-repository ergonomic sugar. - Overriding CRUD via fragments is powerful but affects every caller — prefer JPA entity listeners / `@EntityListeners` / auditing (`@CreatedDate`) or Spring AOP where those fit better, and reserve CRUD overrides for cases those can't express. - Document any CRUD override loudly; it's an invisible behavior change to callers reading only the base contract.

  • Why can't a default method easily use an injected EntityManager?
    A default method executes with only `this` (the proxy) available; there's no Impl instance to receive constructor-injected collaborators. For injected dependencies you need a fragment class.
  • For adding created/modified timestamps on save, would you override save() in a fragment?
    Usually no — prefer JPA auditing (@CreatedDate/@LastModifiedDate with @EntityListeners/AuditingEntityListener) or entity lifecycle callbacks. Reserve a CRUD override for logic those mechanisms can't express, and document it.

saying these in an interview costs you the question

  • Claiming default methods support constructor injection like fragments
  • Saying default methods can cleanly override base save/delete via composition ordering
  • Believing a default method still triggers name-based query derivation

context