Skip to content

Incorrect schema/example with @EmbeddedId + relations (Spring Data REST) #3136

@dpkass

Description

@dpkass

Summary
With JPA @EmbeddedId/@MapsId join entities and Spring Data REST, springdoc generates recursively nested examples instead of compact, reference-like shapes. This misleads client generators and bloats the OpenAPI.

Environment

  • Spring Boot + Spring Data JPA + Spring Data REST (HAL)
  • springdoc: 2.8.14

Minimal model (flattened for clarity)

@Embeddable
class GoalInitiativeImpactId { Long goalId; Long initiativeId; }

@Entity
class Goal {
  @Id @GeneratedValue Long id;
  String name;
  // other fields

  @OneToMany(mappedBy = "goal", cascade = CascadeType.ALL, orphanRemoval = true)
  Set<InitiativeImpactOnGoal> impactsByInitiatives = new HashSet<>();
}

@Entity
class Initiative {
  @Id @GeneratedValue Long id;
  String name;
  // other fields
}

@Entity
class InitiativeImpactOnGoal {
  @EmbeddedId GoalInitiativeImpactId id;
  @ManyToOne @MapsId("goalId") Goal goal;
  @ManyToOne @MapsId("initiativeId") Initiative initiative;
  @Enumerated(EnumType.STRING) ImpactLevel impactLevel;
}

Repositories are exposed via Spring Data REST.


Actual (runtime showcase) — request & response

Request
GET /goals/1

Response (trimmed)

{
  "id": 1,
  "name": { "en-US": "string" },
  "impactsByInitiatives": [
    {
      "impactLevel": "TRIVIAL",
      "_links": {
        "goal": { "href": "/goals/1" },
        "initiative": { "href": "/initiatives/42" }
      }
    }
  ],
  "_links": {
    "self": { "href": "/goals/1" },
    "indicators": { "href": "/goals/1/indicators" },
    "parent": { "href": "/goals/1/parent" }
  }
}

The join entity appears as an item with impactLevel + HAL links.


Generated by springdoc (Swagger UI example) — problematic (trimmed)

{
  "id": 1,
  "name": { "en-US": "string" },
  "description": { "en-US": "string" },
  "workflowStatus": "DRAFT",
  "impactsByInitiatives": [
    {
      "id": { "goalId": 1, "initiativeId": 42 },
      "goal": {
        "id": 1,
        "name": { "en-US": "string" },
        "parent": {
          "id": 7,
          "parent": { /* … nests again … */ }
        },
        "initiatives": [
          {
            "id": 42,
            "name": "string",
            "payments": [ { /* … */ } ],
            "progressLog": [ { /* … */ } ]
          }
        ],
        "children": [ { /* … */ } ]
      },
      "initiative": {
        "id": 42,
        "name": "string",
        "payments": [ { /* … */ } ],
        "progressLog": [ { /* … */ } ]
      },
      "impactLevel": "TRIVIAL"
    }
  ],
  "_links": {
    "self": { "href": "/goals/1" }
  }
}

Issue: deep recursion and full entity expansion inside the join entity, unlike the HAL representation actually returned at runtime.


Expected

Reflect the actual shape for read operations. For write ops we can just use READ_ONLY ourselves.


Workarounds (short)

  • Expose DTOs/projections for REST (flatten associations).
  • Consider a surrogate @Id on the join entity if OpenAPI consumers choke on composite keys.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions