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

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages