Skip to content

huisoo/springdoc-issue-3161-reproduction

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Springdoc OpenAPI Issue #3161 Reproduction Project

Issue Reference: springdoc/springdoc-openapi#3161
Bug: HAL _links field is duplicated in subtypes extending RepresentationModel

πŸ“‹ Table of Contents


Overview

This is a minimal Spring Boot application to reproduce issue #3161 in the Springdoc OpenAPI library.

Core Problem: When a subtype (ExtendedTestDto) extends a DTO (TestDto) that itself extends Spring HATEOAS's RepresentationModel, the _links field is incorrectly duplicated in the OpenAPI schema.


Bug Description

Expected Behavior

"ExtendedTestDto": {
  "allOf": [
    { "$ref": "#/components/schemas/TestDto" },
    {
      "type": "object",
      "properties": {
        "otherField": { "type": "string" }
        // _links should NOT appear here (inherited from TestDto)
      }
    }
  ]
}

Actual Behavior (Bug)

"ExtendedTestDto": {
  "allOf": [
    { "$ref": "#/components/schemas/TestDto" },
    {
      "type": "object",
      "properties": {
        "otherField": { "type": "string" },
        "_links": {                           // ❌ Duplicate!
          "$ref": "#/components/schemas/Links"
        }
      }
    }
  ]
}

Issue: The _links field appears in both TestDto (parent) and ExtendedTestDto (child), causing duplication.


Test Results

Tested across multiple version combinations:

Spring Boot springdoc-openapi Spring Framework Result Notes
3.4.1 2.7.0 6.2.x βœ… Works Recommended baseline
3.5.9 2.8.14 6.1.x ❌ Bug Originally reported version
3.5.9 2.8.15 6.1.x ❌ Bug Bug persists in latest 2.x
4.0.1 3.0.1 6.2.x ❌ Bug Bug exists in Spring Boot 4.x

Key Findings

  1. βœ… springdoc-openapi 2.7.0 works correctly (no bug)
  2. ❌ springdoc-openapi 2.8.14+ exhibits the bug
  3. ❌ Spring Boot 4.0.1 + springdoc-openapi 3.0.1 still has the bug
  4. πŸ” Likely regression between 2.7.0 β†’ 2.8.14

Quick Start

Prerequisites

  • Java 17+
  • Gradle 8.x (included via wrapper)
  • Git (optional)

Installation

# 1. Clone repository (or download ZIP)
git clone https://github.com/YOUR_USERNAME/springdoc-issue-3161-reproduction.git
cd springdoc-issue-3161-reproduction

# 2. Run tests with default configuration (3.5.9 + 2.8.14 - bug version)
./gradlew clean test --tests SimpleBugReproductionTest

# 3. Start application (optional)
./gradlew bootRun

# 4. Check in browser
# - Swagger UI: http://localhost:8080/swagger-ui.html
# - OpenAPI JSON: http://localhost:8080/v3/api-docs

Testing Different Versions

This project uses a single branch approach. Modify build.gradle to test different version combinations.

Option 1: Test Working Version (3.4.1 + 2.7.0)

Edit build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.1'  // ← Change here
    id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'  // ← Change here
    // ... rest remains same
}

Run test:

./gradlew clean test --tests SimpleBugReproductionTest

Expected output:

====================================
πŸ› Testing with MockMvc
====================================

Spring Boot Version: 3.4.1
Springdoc OpenAPI Version: 2.7.0

βœ… Bug NOT reproduced
   ExtendedTestDto schema is correct

Option 2: Test Bug Version (3.5.9 + 2.8.14) - Default

build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.9'  // Default
    id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14'  // Default
    // ... rest remains same
}

Run test:

./gradlew clean test --tests SimpleBugReproductionTest

Expected output:

====================================
πŸ› Testing with MockMvc
====================================

Spring Boot Version: 3.5.9
Springdoc OpenAPI Version: 2.8.14

❌ Bug reproduced!
   ExtendedTestDto has both:
   1. allOf: [TestDto] (inheritance)
   2. properties._links (duplicate)

   Expected: _links should only be in TestDto (via RepresentationModel)
   Actual: _links appears in both TestDto AND ExtendedTestDto

Option 3: Test Latest 2.x (3.5.9 + 2.8.15)

build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.9'
    id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.15'  // ← Latest 2.x
    // ... rest remains same
}

Run test:

./gradlew clean test --tests SimpleBugReproductionTest

Expected output: ❌ Bug reproduced (bug persists in 2.8.15)


Option 4: Test Spring Boot 4.x (4.0.1 + 3.0.1)

build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '4.0.1'  // ← Spring Boot 4.x
    id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1'  // ← 3.x version
    // ... rest remains same
}

Run test:

./gradlew clean test --tests SimpleBugReproductionTest

Expected output: ❌ Bug reproduced (bug exists in Spring Boot 4.x)


Reproduction Steps

Automated Testing

# 1. Run test
./gradlew clean test --tests SimpleBugReproductionTest

# 2. Run with detailed logs
./gradlew clean test --tests SimpleBugReproductionTest --info

# 3. Run all tests
./gradlew clean test

Manual Verification

# 1. Start application
./gradlew bootRun

# 2. Check OpenAPI spec in terminal
curl http://localhost:8080/v3/api-docs | jq '.components.schemas.ExtendedTestDto'

# 3. Check Swagger UI in browser
open http://localhost:8080/swagger-ui.html

OpenAPI Schema Validation

Check the ExtendedTestDto schema in OpenAPI JSON (/v3/api-docs):

βœ… Correct (No Bug):

"ExtendedTestDto": {
  "allOf": [
    { "$ref": "#/components/schemas/TestDto" },
    {
      "type": "object",
      "properties": {
        "otherField": { "type": "string" }
        // No _links field here!
      }
    }
  ]
}

❌ Bug (Duplicate _links):

"ExtendedTestDto": {
  "allOf": [
    { "$ref": "#/components/schemas/TestDto" },
    {
      "type": "object",
      "properties": {
        "otherField": { "type": "string" },
        "_links": {                           // Duplicate!
          "$ref": "#/components/schemas/Links"
        }
      }
    }
  ]
}

Project Structure

springdoc-issue-3161-reproduction/
β”œβ”€β”€ README.md                           # This file
β”œβ”€β”€ build.gradle                        # Gradle build configuration
β”œβ”€β”€ settings.gradle                     # Project settings
β”œβ”€β”€ .gitignore                          # Git ignore file
β”œβ”€β”€ gradlew                            # Gradle Wrapper (Unix)
β”œβ”€β”€ gradlew.bat                        # Gradle Wrapper (Windows)
β”œβ”€β”€ gradle/
β”‚   └── wrapper/
β”‚       β”œβ”€β”€ gradle-wrapper.jar         # Gradle Wrapper JAR
β”‚       └── gradle-wrapper.properties  # Gradle version config
└── src/
    β”œβ”€β”€ main/
    β”‚   β”œβ”€β”€ java/io/github/huisoo/reproduction/springdocissue3161/
    β”‚   β”‚   β”œβ”€β”€ SpringdocIssue3161ReproductionApplication.java  # Main class
    β”‚   β”‚   β”œβ”€β”€ controller/
    β”‚   β”‚   β”‚   └── TestController.java                        # /hello endpoint
    β”‚   β”‚   └── dto/
    β”‚   β”‚       β”œβ”€β”€ TestDto.java                               # Parent DTO
    β”‚   β”‚       └── ExtendedTestDto.java                       # Child DTO (bug demo)
    β”‚   └── resources/
    β”‚       └── application.properties                          # Spring config
    └── test/
        └── java/io/github/huisoo/reproduction/springdocissue3161/
            └── SimpleBugReproductionTest.java                  # Reproduction test

Key Code

TestDto.java

@Data
@EqualsAndHashCode(callSuper = false)
@Schema(
    description = "Base test DTO extending RepresentationModel",
    subTypes = { ExtendedTestDto.class }
)
public class TestDto extends RepresentationModel<TestDto> {
    
    @Schema(description = "ID field")
    private Long id;
    
    @Schema(description = "Name field")
    private String name;
    
    // _links is automatically provided by RepresentationModel
}

ExtendedTestDto.java

@Data
@EqualsAndHashCode(callSuper = true)
@Schema(
    description = "Extended test DTO",
    allOf = { TestDto.class }  // ← Inherits via allOf
)
public class ExtendedTestDto extends TestDto {
    
    @Schema(description = "Additional field specific to ExtendedTestDto")
    private String otherField;
    
    // _links should be inherited from TestDto (parent)
    // Bug: _links incorrectly appears here as duplicate!
}

TestController.java

@RestController
@Tag(name = "Test", description = "Test API for reproducing springdoc-openapi issue #3161")
public class TestController {

    @GetMapping("/hello")
    @Operation(
        summary = "Get ExtendedTestDto",
        description = "Returns an ExtendedTestDto to demonstrate _links duplication bug"
    )
    public ExtendedTestDto hello() {
        ExtendedTestDto dto = new ExtendedTestDto();
        dto.setId(1L);
        dto.setName("Test");
        dto.setOtherField("Additional Data");
        
        // Add HATEOAS link
        Link selfLink = linkTo(methodOn(TestController.class).hello()).withSelfRel();
        dto.add(selfLink);
        
        return dto;
    }
}

SimpleBugReproductionTest.java

@SpringBootTest(classes = SpringdocIssue3161ReproductionApplication.class)
@AutoConfigureMockMvc
public class SimpleBugReproductionTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void reproduceBugWithMockMvc() throws Exception {
        // Get OpenAPI spec
        MvcResult result = mockMvc.perform(get("/v3/api-docs"))
                .andExpect(status().isOk())
                .andReturn();
        
        String openApiJson = result.getResponse().getContentAsString();
        JsonNode rootNode = objectMapper.readTree(openApiJson);
        
        // Check ExtendedTestDto schema
        JsonNode extendedTestDto = rootNode
            .path("components")
            .path("schemas")
            .path("ExtendedTestDto");
        
        // Bug check: ExtendedTestDto should NOT have duplicate _links
        boolean hasAllOf = extendedTestDto.has("allOf");
        boolean hasOwnLinks = extendedTestDto.path("properties").has("_links");
        
        // Assertion: _links should NOT exist (inherited from TestDto)
        assertThat(hasOwnLinks)
            .as("ExtendedTestDto should NOT have own _links property")
            .isFalse();
    }
}

Debugging Tips

Check Dependency Versions

# View dependency tree
./gradlew dependencies --configuration runtimeClasspath | grep -E "spring-boot|springdoc"

# Check specific dependency version
./gradlew dependencyInsight --dependency springdoc-openapi-starter-webmvc-ui
./gradlew dependencyInsight --dependency spring-boot-starter-web

Compare OpenAPI Specs

# Save working version (3.4.1 + 2.7.0) spec
curl http://localhost:8080/v3/api-docs | jq . > openapi-working.json

# Modify build.gradle to bug version...

# Save bug version (3.5.9 + 2.8.14) spec
curl http://localhost:8080/v3/api-docs | jq . > openapi-bug.json

# Compare differences
diff -u openapi-working.json openapi-bug.json

Extract ExtendedTestDto Schema Only

curl -s http://localhost:8080/v3/api-docs | \
  jq '.components.schemas.ExtendedTestDto'

Enable Debug Logging

src/main/resources/application.properties:

# Enable detailed logging
logging.level.org.springdoc=DEBUG
logging.level.org.springframework.hateoas=DEBUG
logging.level.io.swagger=DEBUG

Endpoints

Endpoint Description
GET /hello Returns ExtendedTestDto for testing
GET /v3/api-docs OpenAPI 3.0 specification (JSON)
GET /swagger-ui.html Swagger UI

Version Compatibility Matrix

Spring Boot & Spring Framework

Spring Boot Spring Framework Notes
3.4.x 6.2.x Supports LiteWebJarsResourceResolver
3.5.x 6.1.x Does not support LiteWebJarsResourceResolver
4.0.x 6.2.x Spring Boot 4.x (latest)

springdoc-openapi Compatibility

springdoc-openapi Recommended Spring Boot Spring Framework Issue #3161 Status
2.7.0 3.4.x 6.2.x βœ… Works
2.8.14 3.5.x 6.1.x ❌ Bug
2.8.15 3.5.x 6.1.x ❌ Bug
3.0.1 4.0.x 6.2.x ❌ Bug

Contributing

Contributions to improve this reproduction or test additional versions are welcome!

How to Contribute

  1. Fork this repository
  2. Create a feature branch (git checkout -b feature/test-new-version)
  3. Commit your changes (git commit -m 'Add test for version X.Y.Z')
  4. Push to the branch (git push origin feature/test-new-version)
  5. Open a Pull Request

Reporting Test Results

If you test a new version combination, please include:

  • Spring Boot version
  • springdoc-openapi version
  • Spring Framework version (auto-determined)
  • Bug reproduction status (βœ… Works / ❌ Bug)
  • Test execution logs (optional)

License

This project is licensed under the MIT License.


References


Contact


Acknowledgments

This reproduction project is created with appreciation for the excellent work of the springdoc-openapi team, and aims to contribute to resolving this issue.


Last Updated: 2026-01-10

About

Minimal reproduction project for springdoc-openapi issue #3161 - HAL _links duplication bug

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages