skip to content

You declare a Spring Data repository as just an interface with no body. Who provides the actual implementation, and when?

level: juniorimportance: must knowfreq 72%

answer

  1. Interface only, no Impl
  2. Proxy generated at startup
  3. SimpleJpaRepository = CRUD base
  4. Query methods derived from name / @Query
  5. Fail fast at context load

basics

~20 s

You never write the implementation. Spring Data creates it for you automatically at application startup — it generates a proxy object that implements your repository interface and wires it into the Spring container as a bean.

solid answer

~40 s

A Spring Data repository is a plain interface (e.g. extending JpaRepository). You never implement it. At startup, Spring Data's infrastructure scans for these interfaces and, for each one, produces a runtime proxy object that implements the interface. That proxy becomes the Spring bean you inject. When you call a method on it, the proxy either forwards to a built-in base implementation (SimpleJpaRepository for CRUD methods like save/findById) or executes a query it derived from the method name or from an @Query annotation. Because everything is generated at container startup, misconfigured queries or missing base packages typically fail fast when the context loads, not at first use. The developer's job is only to declare methods; the framework supplies behavior.

go deeper

for a junior

Must know that you write only the interface and the framework generates the implementation at startup.

for a middle

Should be able to name SimpleJpaRepository as the CRUD base and explain derived-vs-@Query routing.

for a senior

Frames it as proxy generation over interfaces with fail-fast startup semantics.

for a principal

Connects declarative repositories to the broader programming model and startup-cost / bootstrap-mode trade-offs.

**The core idea.** In Spring Data you write repositories as *interfaces*, not classes. For example: ```java public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); } ``` There is no `UserRepositoryImpl` that you write. Yet you can `@Autowired UserRepository` and call `save`, `findById`, and `findByEmail`. Something must be providing a concrete object. **What actually happens.** During Spring's application-context startup, Spring Data's infrastructure (triggered by `@EnableJpaRepositories`, or auto-configured by Spring Boot) finds every interface that extends a Spring Data `Repository` marker (like `JpaRepository`, `CrudRepository`, `PagingAndSortingRepository`). For each interface it creates a **proxy** — a dynamically generated object that implements that interface — and registers it as a bean in the container. That proxy is what gets injected when you `@Autowired`. **Where the behavior comes from.** The proxy does not contain hand-written logic for each method. Instead, when you call a method it routes the call: - **CRUD / base methods** (`save`, `findById`, `delete`, `count`, …) go to a framework-provided base class instance — for JPA that is `SimpleJpaRepository`, which uses the `EntityManager` under the hood. - **Query methods** you declared (`findByEmail`) are handled by a query Spring Data built at startup, either by *deriving* it from the method name (`findByEmail` → `where email = ?`) or from an explicit `@Query`. **Terms defined.** - *Proxy*: an object generated at runtime that implements an interface and intercepts calls to it so the framework can insert behavior. - *Bean*: an object managed by the Spring container (created, configured, injected for you). - *SimpleJpaRepository*: Spring Data JPA's default implementation of the standard CRUD/paging operations. **When it happens / why it matters.** All this generation occurs at *startup*, not lazily at first call (with the default bootstrap mode). A key practical consequence: many mistakes — an invalid `@Query`, a derived method name that references a non-existent property, or repositories in a package the scanner doesn't cover — surface as a startup failure, which is exactly what you want (fail fast). **Gotcha.** A repository interface that is *not* on a scanned base package will simply have no bean created, and injection fails with 'no qualifying bean' — people often mistake this for the framework being 'broken' when really the interface was outside the scanned packages.

  • If you never write the implementation, how does findByEmail know what SQL/JPQL to run?
    At startup Spring Data parses the method name into a query (property 'email', keyword equality) or reads an explicit @Query, builds a RepositoryQuery object, and the proxy invokes it when the method is called.
  • What happens if the repository interface is in a package not covered by the scan?
    No proxy bean is created for it, so injecting it fails with a 'no qualifying bean of type' error at startup — a configuration/base-package problem, not a framework bug.

context