Spring Data REST: Expose JPA Repositories as REST APIs
Spring Data REST auto-exposes JPA repositories as RESTful APIs.
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
- ✓Java 17+
- ✓Spring Boot 3.x
- ✓Basic knowledge of JPA and Spring Data
- ✓Familiarity with RESTful APIs
- Spring Data REST automatically generates REST endpoints from JPA repositories, saving boilerplate.
- Use @RepositoryRestResource to customize paths and security.
- Avoid exposing repositories directly in production without proper validation and security.
- Performance pitfalls: N+1 queries and excessive endpoints for deep associations.
- Best for CRUD-heavy internal APIs; avoid for complex business logic or fine-grained access control.
Think of Spring Data REST as a waiter who automatically brings you the menu items you ask for, without you having to go to the kitchen. You tell it what data you have (JPA repositories), and it creates ready-to-use API endpoints. But if you need a custom recipe or special handling, you still need a chef (a regular controller).
You've built a solid Spring Boot app with JPA repositories. Now you need REST APIs. The quickest path? Spring Data REST. It automatically exposes your repositories as RESTful endpoints with HAL+JSON, pagination, sorting, and even HATEOAS links. No controllers, no manual mapping. Sounds like magic, right?
I've seen teams adopt Spring Data REST for rapid prototyping and internal tools, only to hit production walls when they needed custom validation, security, or performance tuning. It's a double-edged sword: it accelerates development but hides complexity. In this guide, I'll walk you through setting it up, the production gotchas I've encountered (and fixed), and when you should stick to traditional controllers.
- How to expose repositories with minimal config
- Customizing endpoints with annotations and projections
- Production debugging for common issues like N+1 queries and circular references
- The security and validation pitfalls that the official docs gloss over
By the end, you'll know exactly where Spring Data REST shines and where it'll burn you.
What Is Spring Data REST?
Spring Data REST is a module that automatically exposes Spring Data repositories as RESTful endpoints. Add a JPA repository interface, and you get GET, POST, PUT, PATCH, DELETE endpoints with pagination, sorting, and HATEOAS links. No controllers, no manual mapping.
Here's the minimal setup:
- Add the dependency:
- ```xml
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-rest</artifactId>
- </dependency>
- ```
- Create a JPA entity and repository:
- ```java
- @Entity
- public class Book {
- @Id @GeneratedValue
- private Long id;
- private String title;
- private String author;
- // getters and setters
- }
public interface BookRepository extends JpaRepository<Book, Long> {} ```
- That's it. Start the app, and you'll have endpoints at
/books.
Spring Data REST uses HAL (Hypertext Application Language) as the default media type. Responses include _links for navigation. For example, a GET to /books/1 returns: ``json { "title": "The Pragmatic Programmer", "author": "Andy Hunt", "_links": { "self": { "href": "http://localhost:8080/books/1" }, "book": { "href": "http://localhost:8080/books/1" } } } ``
This is great for exploratory APIs and internal tools. But remember: every repository you create becomes a public endpoint unless you explicitly restrict it. I've seen teams accidentally expose sensitive data because they forgot to secure a repository.
Customizing Endpoints with Annotations
You don't have to accept the default endpoint names. Use @RepositoryRestResource to customize the path, collection resource, and item resource names.
``java @RepositoryRestResource(collectionResourceRel = "books", path = "books") public interface BookRepository extends JpaRepository<Book, Long> {} ``
To hide a repository entirely (e.g., for internal join tables), set exported = false: ``java @RepositoryRestResource(exported = false) public interface InternalLogRepository extends JpaRepository<InternalLog, Long> {} ``
You can also expose only specific methods using @RestResource: ``java public interface BookRepository extends JpaRepository<Book, Long> { @RestResource(path = "byAuthor", rel = "byAuthor") List<Book> findByAuthor(@Param("author") String author); } ` This creates a search endpoint at /books/search/byAuthor?author=...`.
One thing the docs don't emphasize: @Param is required for query method parameters to be exposed as request parameters. Without it, your custom search methods won't work via REST.
Also, avoid exposing methods that return void or non-entity types — they won't be mapped. This includes delete methods that return void; use void with caution because Spring Data REST will expose DELETE endpoints for item resources, not for custom methods.delete()
Projections and Excerpts: Control What Data Is Exposed
By default, Spring Data REST returns full entity representations. For security or performance, you might want to return only a subset of fields. Use projections.
Create a projection interface: ``java @Projection(name = "summary", types = { Book.class }) public interface BookSummary { String getTitle(); String getAuthor(); // No price, no internal notes } ``
Users can then access /books/1?projection=summary to get only the projected fields.
You can also set a default projection via @RepositoryRestResource(excerptProjection = BookSummary.class). This applies the projection to collection resources (but not item resources unless you query with the projection parameter).
What the docs don't tell you: projections work by creating a proxy that delegates to the entity's getters. If your getters have side effects (e.g., lazy loading), you could trigger database calls. Also, projections don't support nested objects well — you'll need to use DTOs or custom controllers for complex aggregations.
Another gotcha: projections are only applied when explicitly requested via query parameter. If you set an excerptProjection, it affects the collection resource but not the item resource. For item resources, you must still pass ?projection=.... This inconsistency has caused confusion in my teams.
Validation and Error Handling
Spring Data REST integrates with Bean Validation. Add @Valid annotations on your entities, and Spring Data REST will validate on POST/PUT. For example: ``java @Entity public class Book { @Id @GeneratedValue private Long id; @NotBlank(message = "Title is required") private String title; // ... } ``
When validation fails, Spring Data REST returns a 400 Bad Request with error details. But the default error format is verbose and may leak stack traces. You should customize the error handling using @ControllerAdvice or by providing a RepositoryRestConfigurer.
``java @Component public class RestConfig implements RepositoryRestConfigurer { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { config.setReturnBodyOnCreate(true); config.setReturnBodyOnUpdate(true); } } ``
One production issue: Spring Data REST does NOT automatically apply validation on PATCH (partial updates). If you rely on @Valid for PATCH, it may not trigger. You need to manually validate in a custom handler or use @Validated on the repository.
Also, error responses for constraint violations are not always user-friendly. I've seen stack traces containing SQL errors leak to clients. Always configure a custom error controller or use ResponseEntityExceptionHandler to sanitize responses.
What the Official Docs Won't Tell You
1. The N+1 Query Problem
Spring Data REST eagerly resolves associations for every item in a collection. If you have a Book with an Author relation, a GET to /books will execute one query for the books and then one query per book to fetch the author. This is the classic N+1 problem. The docs mention spring.jpa.open-in-view but that only delays the issue. Fix: use @EntityGraph or @Query with JOIN FETCH in your repository.
2. Circular References Kill Your API
Bidirectional relationships cause infinite recursion. For example, Book has Author, and Author has List<Book>. Spring Data REST's default Jackson serialization will stack overflow. The docs suggest @JsonIgnore, but that's a blunt tool. Better: use @JsonManagedReference on the parent and @JsonBackReference on the child, or use projections to break the cycle.
3. Security is Not Automatic
Spring Data REST respects Spring Security's method-level security if you enable it. But the default is to expose everything. You must explicitly add @PreAuthorize on repository methods. Even then, the search endpoints may bypass security if not configured. I've seen a team assume that securing the controller layer secures the repository — but there is no controller layer. You must secure the repository itself.
4. Performance: No Field Selection
Clients cannot request only specific fields (like GraphQL). They get the full entity or a projection. If you have large entities, this wastes bandwidth. Consider using custom controllers with DTOs for high-traffic endpoints.
5. HATEOAS Links Can Be Misleading
The _links object includes a self link and a collection link. But if you change the base path, the links may still point to the old path. Always test with the actual deployment URL.
When to Use Spring Data REST vs. Traditional Controllers
Spring Data REST is not a silver bullet. Here's my rule of thumb:
Use Spring Data REST when: - You need a quick CRUD API for internal tools or admin panels. - Your API is read-heavy with simple write operations. - You want to prototype a data layer rapidly. - You don't need fine-grained access control per field.
Avoid Spring Data REST when: - You have complex business logic on writes (e.g., validation across entities, state machines). - You need fine-grained security (e.g., user A can see field X but not Y). - You need to aggregate data from multiple sources. - You have deep object graphs that cause N+1 or circular references. - You want to support partial updates with custom validation.
In production, I've seen teams start with Spring Data REST and then gradually replace it with custom controllers as requirements grow. That's fine — you can mix both. Just be aware that once you introduce custom controllers, you lose the automatic HATEOAS links and consistency.
A common pattern: use Spring Data REST for read-only reference data (e.g., list of countries, categories) and custom controllers for transactional operations (e.g., placing an order).
The Midnight Outage: Exposed Internal IDs and Unbounded Endpoints
- Never expose repositories without explicit security; default is all exported.
- Use @RepositoryRestResource(exported = false) for sensitive entities.
- Always set max-page-size to prevent unbounded queries.
- Test with a security audit tool to verify endpoint exposure.
- Consider using DTOs and custom controllers for complex business logic.
curl -v http://localhost:8080/api/entitiesCheck application logs for 'Exposing repository' messages.| File | Command / Code | Purpose |
|---|---|---|
| BookRepository.java | public interface BookRepository extends JpaRepository | What Is Spring Data REST? |
| CustomBookRepository.java | @RepositoryRestResource(collectionResourceRel = "books", path = "books") | Customizing Endpoints with Annotations |
| BookSummaryProjection.java | @Projection(name = "summary", types = { Book.class }) | Projections and Excerpts |
| CustomErrorController.java | @RestControllerAdvice | Validation and Error Handling |
| EntityGraphExample.java | public interface BookRepository extends JpaRepository | What the Official Docs Won't Tell You |
| CustomControllerExample.java | @RestController | When to Use Spring Data REST vs. Traditional Controllers |
Key takeaways
Interview Questions on This Topic
How does Spring Data REST expose JPA repositories as REST APIs?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
That's Hibernate & JPA. Mark it forged?
5 min read · try the examples if you haven't