feat(docservice): Support Jackson polymorphism annotations
Motivation
This pull request implements support for Jackson's polymorphism annotations (@JsonTypeInfo, @JsonSubTypes) in DocService, as requested in the community (issue #6313). Currently, DocService does not correctly generate documentation for annotated services that use inheritance in their DTOs, leading to incomplete specifications. This change adds a new DescriptiveTypeInfoProvider to resolve these polymorphic types and generate accurate JSON Schemas.
However, this feature has uncovered significant and complex build stability issues when running a full parallel build (./gradlew clean build --parallel). This PR serves as both the implementation of the feature and a concrete test case for discussing the build instability it triggers.
Modifications
- Added
JacksonPolymorphismTypeInfoProvider: A new provider that uses pure Java reflection to safely inspect@JsonTypeInfoand@JsonSubTypesannotations. It is registered via Java's SPI mechanism to be discoverable byDocService. - Added
DiscriminatorInfo: A new data class to hold polymorphism metadata extracted from the annotations. - Consolidated Type Utilities: General-purpose type conversion logic (e.g.,
toTypeSignature) was moved from a separateDocServiceTypeUtilintoAnnotatedDocServicePluginfor better cohesion. - Updated
StructInfo: Modified to includeoneOfanddiscriminatorfields to carry polymorphism information. - Updated
JsonSchemaGenerator: The generator now recognizes the new fields inStructInfoand correctly produces JSON Schema withoneOfanddiscriminatorproperties. - Added
PolymorphismDocServiceExample: A new example service to demonstrate and manually verify the feature.
Result
DocServicecan now correctly generate documentation for annotated services that use polymorphic types with Jackson. The resulting JSON Schema will contain the appropriateoneOfanddiscriminatorfields.- ~~Known Issue: This change is known to trigger build instability in the project's CI environment. A detailed summary of the investigation is provided here : #6369~~
Example usage
also, you can try this at PolymorphismDocServiceExample
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "species")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
interface Animal {
// ...
}
"structs" : [ {
"name" : "example.armeria.server.animal.PolymorphismDocServiceExample$Animal",
"fields" : [ ],
"descriptionInfo" : {
"docString" : "",
"markup" : "NONE"
},
"oneOf" : [ "example.armeria.server.animal.PolymorphismDocServiceExample$Dog", "example.armeria.server.animal.PolymorphismDocServiceExample$Cat" ],
"discriminator" : {
"propertyName" : "species",
"mapping" : {
"dog" : "#/definitions/example.armeria.server.animal.PolymorphismDocServiceExample$Dog",
"cat" : "#/definitions/example.armeria.server.animal.PolymorphismDocServiceExample$Cat"
}
}
However, this feature has uncovered significant and complex build stability issues when running a full parallel build (./gradlew clean build --parallel).
I will investigate it. It seems like your changes aren't related to the failure, so please feel free to change the draft status when you are ready.
Codecov Report
:x: Patch coverage is 82.48588% with 62 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 74.11%. Comparing base (8150425) to head (4823d7b).
:warning: Report is 182 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #6370 +/- ##
============================================
- Coverage 74.46% 74.11% -0.35%
- Complexity 22234 23016 +782
============================================
Files 1963 2063 +100
Lines 82437 86197 +3760
Branches 10764 11334 +570
============================================
+ Hits 61385 63889 +2504
- Misses 15918 16888 +970
- Partials 5134 5420 +286
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
🔍 Build Scan® (commit: 57d4302edaf039a924fd3c2c4e31ad6728fd6b16)
| Job name | Status | Build Scan® |
|---|---|---|
| build-ubicloud-standard-16-jdk-8 | ✅ | https://ge.armeria.dev/s/cfsotxxiuclni |
| build-ubicloud-standard-16-jdk-25 | ✅ | https://ge.armeria.dev/s/67umwifxcvx7c |
| build-ubicloud-standard-16-jdk-21-snapshot-blockhound | ✅ | https://ge.armeria.dev/s/bsp2mpjuv4rgu |
| build-ubicloud-standard-16-jdk-17-min-java-17-coverage | ❌ (failure) | https://ge.armeria.dev/s/v2ztmqjwh5y3s |
| build-ubicloud-standard-16-jdk-17-min-java-11 | ❌ (failure) | https://ge.armeria.dev/s/m3tj3hvz2md6g |
| build-ubicloud-standard-16-jdk-17-leak | ✅ | https://ge.armeria.dev/s/7pm65nii5xmte |
| build-ubicloud-standard-16-jdk-11 | ✅ | https://ge.armeria.dev/s/dxfvdk3bf34aq |
| build-macos-latest-jdk-25 | ✅ | https://ge.armeria.dev/s/bkdazrw3gvh5a |
I found a couple of issues in the generated JSON schema:
- There are many duplicate definitions because each method has its own ID and definitions.
[ { "$id": "...", "definitions": { ... } // duplicate definitions }, { "$id": "...", "definitions": { ... } // duplicate definitions } ] - The "Cat" and "Dog" definitions are missing the
speciesproperty, which is causing Autocomplete to fail.
To address this, I propose the following:
- Use a root object with "$defs/methods" and "$defs/models" to put all methods and structs a single time.
- It's worth noting that "definitions" is deprecated, as mentioned in the JSON Schema draft specification. https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-00#rfc.appendix.G
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "...", "title": "...", "$defs": { "methods": { "processAnimal": { "$id": "com.linecorp.armeria.server.docs.PolymorphismDocServiceTest$AnimalService/processAnimal/POST", "title": "processAnimal", "type": "object", "properties": { "animal": { "$ref": "#/$defs/models/Animal" } }, "required": [ "animal" ] }, "processZoo": { ... } }, "models": { "Animal": { "type": "object", "oneOf": [ { "$ref": "#/$defs/models/Dog" }, { "$ref": "#/$defs/models/Cat" } ], "discriminator": { "propertyName": "species", "mapping": { "dog": "#/$defs/models/Dog", "cat": "#/$defs/models/Cat" } } }, "Cat": { "type": "object", "properties": { "species": { "type": "string" }, "name": { "type": "string" }, "likesTuna": { "type": "boolean" }, "scratchPost": { "$ref": "#/$defs/models/Toy" }, "vetRecord": { "$ref": "#/$defs/models/VetRecord" } }, "required": [ "name", "likesTuna", "scratchPost", "vetRecord" ] }, "Dog": { ... }, ... } } } - Update
RequestBody.tsxto align with the new schema format. (I might help you if you are not familiar with the frontend)
Please, let me know your opinion. 🙇
Use a root object with "$defs/methods" and "$defs/models" to put all methods and structs a single time.
* It's worth noting that "definitions" is deprecated, as mentioned in the JSON Schema draft specification
Thank you for your Review !
I've changed JsonSchemaGenerator based on your feedback, and I agree that the new structure is better than duplicated definitions . Here is the new output :
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "com.....$AnimalService",
"title": "com....$AnimalService",
"$defs": {
"models": {
"com....$Animal": {
"type": "object",
"title": "com....$Animal",
"oneOf": [
{
"$ref": "#/$defs/models/com...$Dog"
},
{
"$ref": "#/$defs/models/com.l...$Cat"
}
],
"discriminator": {
"propertyName": "species",
"mapping": {
"dog": "#/$defs/models/com....$Dog",
"cat": "#/$defs/models/com.....$Cat"
}
}
},
"com....$Cat": {
"type": "object",
"title": "com....$Cat",
"properties": {
"species": {
"type": "string"
},
"name": {
"type": "string"
},
"likesTuna": {
"type": "boolean"
},
"scratchPost": {
"$ref": "#/$defs/models/com....$Toy"
},
"vetRecord": {
"$ref": "#/$defs/models/com....$VetRecord"
}
},
"required": [ "name", "likesTuna", "scratchPost", "vetRecord", "species" ] // Should "species" be first?
},
"...": "..."
}, //models
"methods": {
"processAnimal": {
"$id": "com....$AnimalService/processAnimal/POST",
"title": "processAnimal",
"additionalProperties": false,
"type": "object",
"properties": {
"animal": {
"$ref": "#/$defs/models/com....$Animal"
}
},
"required": [ "animal" ]
},
"...": "..."
} //methods
}
}
However, this change caused the existing GrpcDocServiceJsonSchemaTest to fail. This brings me to my main question:
Should I maintain backward compatibility for the gRPC schema, or should I update it to use the new, unified structure as well?
Also, as you mentioned, I'm not familiar with the frontend, so I would really appreciate your help with the RequestBody.tsx changes when the time comes.
Should I maintain backward compatibility for the gRPC schema, or should I update it to use the new, unified structure as well?
I think we don't have to worry about the compatibility because the browser will fetch the new JSON schema and use it for the autocompletion.
Also, as you mentioned, I'm not familiar with the frontend, so I would really appreciate your help with the RequestBody.tsx changes when the time comes.
I'm happy to help you. 😉 Will push a commit after the server-side changes are done.
Walkthrough
This PR refactors type signature utilities into a centralized location, introduces polymorphic type support in JSON schemas with discriminator metadata, and modernizes JSON schema generation to use a modular structure with definitions-in-definitions organization.
Changes
| Cohort / File(s) | Summary |
|---|---|
Type Utility Centralization core/src/main/java/com/linecorp/armeria/internal/server/annotation/AnnotatedDocServicePlugin.java, core/src/main/java/com/linecorp/armeria/internal/server/docs/DocServiceTypeUtil.java |
Extracts type-to-TypeSignature conversion logic from AnnotatedDocServicePlugin into new DocServiceTypeUtil utility with predefined constants (VOID, BOOLEAN, INT, LONG, STRING, etc.) and two overloaded toTypeSignature methods handling Java types, Jackson types, collections, maps, and optionals. |
Import Migration core/src/main/java/com/linecorp/armeria/internal/server/annotation/DefaultDescriptiveTypeInfoProvider.java, core/src/main/java/com/linecorp/armeria/internal/server/annotation/ReflectiveDescriptiveTypeInfoProvider.java, core/src/test/java/com/linecorp/armeria/internal/server/annotation/*.java, kotlin/src/test/kotlin/..., scala/scala_2.13/src/test/scala/... |
Updates static imports across test and source files to reference toTypeSignature and type constants from DocServiceTypeUtil instead of AnnotatedDocServicePlugin. |
Polymorphism Infrastructure core/src/main/java/com/linecorp/armeria/server/docs/DiscriminatorInfo.java, core/src/main/java/com/linecorp/armeria/internal/server/docs/JacksonPolymorphismTypeInfoProvider.java, core/src/main/resources/META-INF/services/com.linecorp.armeria.server.docs.DescriptiveTypeInfoProvider |
Introduces DiscriminatorInfo class for OpenAPI discriminator metadata and JacksonPolymorphismTypeInfoProvider implementation that discovers polymorphic types via Jackson annotations, registering the provider via ServiceLoader. |
StructInfo Enhancements core/src/main/java/com/linecorp/armeria/server/docs/StructInfo.java |
Adds oneOf (List<TypeSignature>) and discriminator (DiscriminatorInfo) fields to support polymorphic types; updates constructors, builders, equality, hashing, and toString accordingly. |
JSON Schema Generation Refactoring core/src/main/java/com/linecorp/armeria/server/docs/JsonSchemaGenerator.java, core/src/main/java/com/linecorp/armeria/server/docs/DocService.java |
Restructures schema generation from linear ArrayNode flow to modular ObjectNode with $defs/models and $defs/methods organization; adds generateModels, generateMethods, generateFieldSchema, and getSchemaType methods; supports discriminator injection and polymorphism mapping; updates return type from ArrayNode to ObjectNode. |
Test Infrastructure & Coverage core/src/test/java/com/linecorp/armeria/internal/server/annotation/DocServiceTestUtil.java, core/src/test/java/com/linecorp/armeria/internal/server/annotation/PolymorphismDocServiceTest.java, core/src/test/java/com/linecorp/armeria/server/docs/JsonSchemaGeneratorTest.java, grpc/src/test/java/com/linecorp/armeria/internal/server/grpc/GrpcDocServiceJsonSchemaTest.java |
Adds DocServiceTestUtil for test access to DefaultDescriptiveTypeInfoProvider; introduces comprehensive PolymorphismDocServiceTest with animal DTOs and polymorphic endpoint coverage; refactors JsonSchemaGeneratorTest with helper methods and parameterized specs; updates GrpcDocServiceJsonSchemaTest to use single-schema ObjectNode structure with reference helpers. |
Sequence Diagram
sequenceDiagram
participant User as Client
participant DocService
participant JsonSchemaGenerator
participant JacksonPolymorphismTypeInfoProvider
participant DiscriminatorInfo
participant StructInfo
User->>DocService: Request service specification
DocService->>JsonSchemaGenerator: generate(serviceSpecification)
JsonSchemaGenerator->>JsonSchemaGenerator: Inspect types for polymorphism
JsonSchemaGenerator->>JacksonPolymorphismTypeInfoProvider: Discover polymorphic base types
JacksonPolymorphismTypeInfoProvider->>JacksonPolymorphismTypeInfoProvider: Check `@JsonTypeInfo` & `@JsonSubTypes`
JacksonPolymorphismTypeInfoProvider->>DiscriminatorInfo: Create discriminator mapping
DiscriminatorInfo-->>JacksonPolymorphismTypeInfoProvider: propertyName + subtype mapping
JacksonPolymorphismTypeInfoProvider->>StructInfo: Build StructInfo with oneOf & discriminator
StructInfo-->>JacksonPolymorphismTypeInfoProvider: StructInfo ready
JsonSchemaGenerator->>JsonSchemaGenerator: generateModels() & generateMethods()
JsonSchemaGenerator->>JsonSchemaGenerator: Inject discriminator into polymorphic struct schemas
JsonSchemaGenerator->>JsonSchemaGenerator: Build $defs with models and methods
JsonSchemaGenerator-->>DocService: ObjectNode with unified schema
DocService-->>User: Complete service documentation
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Areas requiring extra attention:
- JsonSchemaGenerator.java: Substantial refactoring of core schema generation pipeline with new modular structure, discriminator wiring, and polymorphism mapping logic
- JacksonPolymorphismTypeInfoProvider.java: New polymorphic type discovery and StructInfo construction logic; requires understanding of Jackson annotation introspection
- StructInfo.java: Addition of polymorphism fields affects constructors, builders, serialization, and equality—verify all propagation paths are correct
- Test coverage validation: Ensure PolymorphismDocServiceTest and updated JsonSchemaGeneratorTest adequately exercise new polymorphism and schema structure changes
Suggested labels
new feature
Suggested reviewers
- trustin
- minwoox
- ikhoon
Poem
🐰 A rabbit hops through types so grand, Polymorphism now hand in hand! Discriminators map the way, Schemas unified to stay. DocService blooms—what a day! 🌸
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 23.66% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title 'feat(docservice): Support Jackson polymorphism annotations' is directly related to the main change: adding support for Jackson's @JsonTypeInfo and @JsonSubTypes annotations in DocService. |
| Description check | ✅ Passed | The description is comprehensive and directly related to the changeset, explaining the motivation, modifications, and results of implementing Jackson polymorphism support in DocService. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.