skip to content

You expose repositories via Spring Data REST with excerpt projections and HAL. What representation-level pitfalls should you anticipate at scale, and how do you mitigate them?

level: principalimportance: nice to knowfreq 22%

answer

  1. excerpt inlining -> N+1 / over-fetch
  2. open @Value projection defeats column-select
  3. clients couple to self/association hrefs
  4. @RestResource(exported=false) + exposure DSL
  5. reads shaped, writes still full entity

basics

~20 s

Watch for excerpt projections that inline heavy associations causing over-fetch/N+1 on large collections, open projections defeating column optimisation, clients coupling to auto-generated URLs, and exposing internal fields. Mitigate with closed projections, method exposure limits, and careful excerpt scope.

solid answer

~50 s

The main risks are representation-and-query coupling. Excerpt projections inline associations into every _embedded item, so a large collection endpoint can trigger N+1 loading or over-fetch unless the excerpt stays lean and closed. Open (@Value SpEL) projections silently materialise whole entities, losing the column-selection benefit. HAL's link-driven identity means clients may over-couple to generated hrefs; @Relation and stable base paths help. Auto-exposure can leak sensitive fields — use the exposure DSL to disable methods and projections to whitelist fields, plus @RestResource(exported=false) on associations you don't want reachable. Switching the default media type to HAL-FORMS globally affects all consumers, so prefer content negotiation. Finally, projections shape reads only, so the write model (full entity) and read model (excerpt) diverge — document it and validate writes independently. For truly complex APIs, consider whether Spring Data REST's convention-driven surface is the right long-term contract versus explicit controllers.

code

java · 20 lines
java
// Hide sensitive fields/associations and trim the write/read surface
@Entity
class User {
    @Id @GeneratedValue Long id;
    String email;
    @RestResource(exported = false)   // never traversable via REST
    String passwordHash;
}

@Component
class Governance implements RepositoryRestConfigurer {
    @Override
    public void configureRepositoryRestConfiguration(
            RepositoryRestConfiguration config, CorsRegistry cors) {
        config.getExposureConfiguration()
              .forDomainType(User.class)
              .withItemExposure((m, methods) -> methods.disable(HttpMethod.DELETE));
        // prefer content negotiation for HAL-FORMS rather than a global default
    }
}

go deeper

for a junior

Not expected at this depth.

for a middle

Can name over-fetch and accidental-exposure risks.

for a senior

Explains closed-vs-open cost, exposure DSL, and read/write divergence with mitigations.

for a principal

Frames the JPA-model-as-contract coupling, governs with contract tests and exposure controls, and knows when to replace the generated surface with explicit controllers.

## Convenience versus control At scale, Spring Data REST's convenience trades against control. The representation layer (HAL, projections, excerpts, HAL-FORMS) has several failure modes worth anticipating: ## Query-cost failure modes - **1. Excerpt-induced over-fetch / N+1.** An `excerptProjection` is applied to *every* item in a collection's `_embedded` block. If that excerpt inlines associations (`@Value("#{target.author.name}")`, or getters returning related entities), each row may trigger a lazy load — the classic N+1 — and the payload balloons. Mitigations: - keep excerpts **closed** and minimal; - ensure associations pulled by the excerpt are fetched with a join (entity graph / `@EntityGraph` on a custom finder) rather than lazily per row; - and reconsider whether the excerpt should inline anything beyond a display label. - **2. Open vs closed projection query cost.** **Closed projections** (getters matching entity properties) let Spring Data restrict the SELECT to those columns. The moment you add a `@Value` SpEL getter, the projection becomes **open** and Spring Data loads the *whole* entity, discarding that optimisation. On hot collection endpoints this is a measurable regression. Prefer closed projections; compute derived values client-side or in a dedicated read model if needed. ## Contract and exposure failure modes - **3. Hypermedia coupling.** HAL expresses identity through `self` hrefs and association links. Clients that string-parse or hardcode these URLs couple to your routing. Stabilise with an explicit `setBasePath`, `@Relation`/`@RestResource(rel=...)` for durable relation names, and treat the link relations — not the URL shapes — as the contract. - **4. Accidental exposure.** Spring Data REST exports every public repository and every association by default. This can surface fields or navigation you never intended (e.g. a `passwordHash`, or a link that lets a client traverse to another aggregate). Mitigations: - `@RestResource(exported = false)` on repositories/associations/fields to hide them; - the `getExposureConfiguration()` DSL to disable HTTP methods (e.g. no DELETE); - and projections to **whitelist** exposed fields rather than relying on default full-entity serialisation. - **5. Read/write model divergence.** Projections and excerpts shape **reads only**. Writes (PUT/PATCH/POST) still target the full entity. So a client that fetched an excerpt does not have the full state needed for a safe PUT — hence Spring Data REST deliberately does not apply excerpts to single-item resources. Document the asymmetry; rely on PATCH for partial updates; validate writes with Bean Validation wired through `configureValidatingRepositoryEventListener`. - **6. Media-type blast radius.** `setDefaultMediaType(HAL_FORMS_JSON)` changes *every* response and can break tooling expecting `hal+json`. Prefer content negotiation so only clients requesting HAL-FORMS get `_templates`. - **7. Contract governance.** Because the API surface is generated from repositories, an innocent entity/repository change (new field, new association, renamed property) silently alters the public representation. Guard with **contract tests** against the HAL/HAL-FORMS output, and consider whether high-value or externally consumed endpoints deserve explicit `@RepositoryRestController`/regular controllers where you own the representation outright. ## Decision framing Spring Data REST + projections/excerpts is excellent for **internal or admin CRUD** where the entity graph *is* the API. For public, high-traffic, or long-lived contracts, the implicit coupling between JPA model and exposed representation becomes a liability, and an explicit DTO/controller layer (or GraphQL) may be the better long-term choice.

  • Why does a closed projection outperform an open one on a large collection endpoint?
    A closed projection (getters matching entity columns) lets Spring Data issue a SELECT for only those columns. An open projection with @Value SpEL forces loading the whole entity per row, so on big collections it multiplies I/O and can trigger lazy-load N+1.
  • When would you abandon Spring Data REST's generated surface for explicit controllers?
    For public, high-traffic, or long-lived external contracts where implicit coupling between the JPA entity graph and the exposed representation is a liability — an explicit DTO/controller (or GraphQL) layer lets you own and evolve the contract independently of the persistence model.
  • How do you keep a generated HAL contract from silently breaking clients when the entity changes?
    Add contract tests asserting the HAL/HAL-FORMS payload shape and link relations, treat relation names (not URL shapes) as the contract, pin relation names with @Relation, and hide non-contract fields/associations with @RestResource(exported=false).

saying these in an interview costs you the question

  • Assuming projections make the endpoint faster regardless of open/closed
  • Believing excerpts are free and never cause N+1
  • Treating auto-generated hrefs as a stable client contract without thought
  • Thinking projections limit what clients can write
  • Switching to HAL-FORMS globally without considering existing consumers

context