# How to Implement Repository Pattern for Data Access Abstraction in Java

> Learn to implement the Repository pattern in Java. Isolate domain logic from data access using a collection-like interface for CRUD operations with Spring Data JPA.

- Repository: [Ilkka Seppälä/java-design-patterns](https://github.com/iluwatar/java-design-patterns)
- Tags: how-to-guide
- Published: 2026-02-27

---

**The Repository pattern isolates domain logic from persistence details by exposing a collection-like interface for CRUD operations, typically implemented via Spring Data JPA to auto-generate data access implementations at runtime.**

The **Repository pattern** is essential for building maintainable Java applications that require clean separation between business logic and data storage mechanisms. In the `iluwatar/java-design-patterns` repository, this pattern is demonstrated through a practical implementation using **Spring Data JPA**, showcasing how to abstract database operations behind a simple interface contract.

## Architecture of the Repository Pattern Implementation

The implementation in `iluwatar/java-design-patterns` consists of four tightly integrated components that demonstrate production-ready data access abstraction. Each component resides in the `repository/src/main/java/com/iluwatar/repository/` directory.

### Domain Entity Definition

The `Person` class represents the core data model as a JPA entity. Located at [`repository/src/main/java/com/iluwatar/repository/Person.java`](https://github.com/iluwatar/java-design-patterns/blob/main/repository/src/main/java/com/iluwatar/repository/Person.java), this POJO maps directly to a database table while remaining completely unaware of repository implementation details.

```java
@Entity
public class Person {
  @Id @GeneratedValue private Long id;
  private String name;
  private String surname;
  private int age;

  public Person(String name, String surname, int age) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }
}

```

This entity uses standard JPA annotations (`@Entity`, `@Id`, `@GeneratedValue`) to define the primary key and persistence metadata, allowing the repository layer to manage its lifecycle without explicit SQL.

### Repository Interface Contract

The `PersonRepository` interface at [`repository/src/main/java/com/iluwatar/repository/PersonRepository.java`](https://github.com/iluwatar/java-design-patterns/blob/main/repository/src/main/java/com/iluwatar/repository/PersonRepository.java) defines the API contract for data access. By extending Spring Data interfaces, you eliminate the need to write boilerplate data access code.

```java
@Repository
public interface PersonRepository
    extends CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> {

  Person findByName(String name);
}

```

**Key interface extensions:**
- **`CrudRepository<Person, Long>`** – Provides standard operations including `save()`, `findAll()`, `findById()`, and `deleteById()`.
- **`JpaSpecificationExecutor<Person>`** – Enables type-safe, composable query specifications using the Criteria API.

Spring automatically generates the concrete implementation at runtime, injecting the necessary `EntityManager` and transaction management.

### Specification Pattern for Query Abstraction

Complex query logic is encapsulated in [`repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java`](https://github.com/iluwatar/java-design-patterns/blob/main/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java) using the **Specification pattern**. This approach keeps query criteria out of the service layer and promotes reuse across the application.

```java
public class PersonSpecifications {

  public static class AgeBetweenSpec implements Specification<Person> {
    private final int from;
    private final int to;

    public AgeBetweenSpec(int from, int to) { 
      this.from = from; 
      this.to = to; 
    }

    @Override
    public Predicate toPredicate(Root<Person> root,
                                 CriteriaQuery<?> query,
                                 CriteriaBuilder cb) {
      return cb.between(root.get("age"), from, to);
    }
  }

  public static class NameEqualSpec implements Specification<Person> {
    private final String name;
    
    public NameEqualSpec(String name) { 
      this.name = name; 
    }

    @Override
    public Predicate toPredicate(Root<Person> root,
                                 CriteriaQuery<?> query,
                                 CriteriaBuilder cb) {
      return cb.equal(root.get("name"), name);
    }
  }
}

```

Each specification implements `toPredicate()` to define criteria using JPA's `CriteriaBuilder`, ensuring compile-time safety and database portability.

### Application Configuration and Bootstrapping

The [`App.java`](https://github.com/iluwatar/java-design-patterns/blob/main/App.java) class at [`repository/src/main/java/com/iluwatar/repository/App.java`](https://github.com/iluwatar/java-design-patterns/blob/main/repository/src/main/java/com/iluwatar/repository/App.java) demonstrates end-to-end usage. It loads Spring's application context from [`repository/src/main/resources/applicationContext.xml`](https://github.com/iluwatar/java-design-patterns/blob/main/repository/src/main/resources/applicationContext.xml), which configures an in-memory H2 datasource and enables component scanning for repository beans.

```java
var context = new ClassPathXmlApplicationContext("applicationContext.xml");
var repo = context.getBean(PersonRepository.class);

// Create
repo.save(new Person("Alice", "Smith", 30));
repo.save(new Person("Bob", "Jones", 45));

// Read
repo.findById(1L).ifPresent(p -> System.out.println(p));

// Update
repo.findByName("Alice").setAge(31);
repo.save(repo.findByName("Alice"));

// Delete
repo.deleteById(2L);

// Specification-based query
List<Person> youngAdults = repo.findAll(
    new PersonSpecifications.AgeBetweenSpec(20, 35));
youngAdults.forEach(System.out::println);

```

This bootstrap code illustrates how the repository provides a **collection-like API** that hides the underlying JPA `EntityManager` and transaction boundaries.

## Benefits of This Repository Pattern Implementation

Implementing the Repository pattern as shown in `iluwatar/java-design-patterns` delivers three critical architectural advantages:

1. **Decoupling** – Business code depends only on the `PersonRepository` interface, not on JPA, Hibernate, or SQL specifics. You can switch from H2 to PostgreSQL without modifying domain logic.
2. **Testability** – Mock implementations of `PersonRepository` can replace the real bean in unit tests, allowing you to test service layers without requiring a live database.
3. **Extensibility** – New query requirements are added as new `Specification` classes without modifying existing repository interfaces or service code, adhering to the Open/Closed Principle.

## Summary

- The **Repository pattern** creates a clean abstraction layer between domain models and data persistence technologies.
- **Spring Data JPA** eliminates boilerplate by auto-implementing repository interfaces that extend `CrudRepository` and `JpaSpecificationExecutor`.
- The **Specification pattern** (as seen in [`PersonSpecifications.java`](https://github.com/iluwatar/java-design-patterns/blob/main/PersonSpecifications.java)) encapsulates reusable query logic using JPA Criteria API for type-safe, composable queries.
- Domain entities like [`Person.java`](https://github.com/iluwatar/java-design-patterns/blob/main/Person.java) remain persistence-ignorant POJOs with simple JPA annotations.
- Configuration via [`applicationContext.xml`](https://github.com/iluwatar/java-design-patterns/blob/main/applicationContext.xml) wires the datasource and enables repository scanning without explicit bean definitions.

## Frequently Asked Questions

### What is the difference between Repository pattern and DAO pattern?

While both abstract data access, the **DAO (Data Access Object)** pattern typically maps one-to-one with database tables and focuses on data persistence mechanics. The **Repository pattern** operates at a higher abstraction level, treating the data access layer as a collection of domain objects and speaking the ubiquitous language of the domain. In `iluwatar/java-design-patterns`, the `PersonRepository` exposes methods like `findByName()` that reflect business concepts rather than database operations.

### Does Spring Data JPA require writing SQL for the Repository pattern?

No. Spring Data JPA derives queries from method names (like `findByName`) and provides the `JpaSpecificationExecutor` interface for programmatic criteria construction. As demonstrated in [`PersonSpecifications.java`](https://github.com/iluwatar/java-design-patterns/blob/main/PersonSpecifications.java), you build queries using the Criteria API (`CriteriaBuilder`, `Predicate`) rather than raw SQL, though you can use `@Query` annotations for complex native SQL when necessary.

### How do you test code that uses the Repository pattern?

You can test service layers by mocking the repository interface using frameworks like Mockito. Since `PersonRepository` is an interface extending `CrudRepository`, you inject mock implementations in unit tests to verify business logic without database dependencies. For integration tests, Spring provides `@DataJpaTest` which configures an embedded database and repository layer automatically.

### Can the Repository pattern be used without Spring Framework?

Yes. The Repository pattern is framework-agnostic. You can implement it using plain JDBC, JPA's `EntityManager` directly, or other frameworks like Quarkus or Micronaut. The `iluwatar/java-design-patterns` implementation uses Spring for convenience, but the core concept—hiding persistence details behind a collection-like interface—applies regardless of the underlying technology stack.