[improve][misc] Migrate Swagger annotations to OpenAPI 3 (Swagger Core v3, jakarta) - #25937
Merged
Merged
Conversation
…e v3, jakarta) Completes the Swagger migration deferred from PIP-472: Swagger 1.6.2 (io.swagger, javax-era, EOL) is replaced with Swagger Core v3 io.swagger.core.v3:swagger-annotations-jakarta:2.2.50 across the whole codebase, and OpenAPI document generation is restored in the Gradle build. Annotations (61 Java files): - @Api -> @tag (+@hidden where v1 had hidden=true), @apioperation -> @operation, @apiresponse(code=, message=, response=, responseContainer=) -> @apiresponse(responseCode=, description=, content=) with @ArraySchema / additionalPropertiesSchema container mappings, @ApiParam -> @parameter (bound params incl. @FormDataParam multipart parts) / @RequestBody (body params), @ApiModel/@ApiModelProperty -> @Schema with requiredMode, @Example/@ExampleProperty -> @ExampleObject. - Map-valued responses use @Schema(type = "object", additionalPropertiesSchema = X.class) - the only form the swagger-core 2.2.50 resolver actually renders; the @Content-level additionalPropertiesSchema = @Schema(implementation = X.class) form silently emits additionalProperties: {}. - Protobuf/lightproto types in @Schema(implementation=) are replaced with type = "object" schemas (Jackson introspection crashes on them and the endpoints emit protobuf JSON anyway). - BaseGenerateDocumentation reflection ported to @Schema (generateDocByApiModelProperty -> generateDocBySchema and friends). - Removed the unused NoSwaggerDocumentation marker annotation and stale workaround comments for swagger-api/swagger-core#449 and swagger-ui#558 (both fixed upstream years ago); nested map schemas are now expressed precisely. - Fixed malformed example JSON payloads that degraded to plain strings in generated docs (trailing comma, stray '+', missing comma). Build: - Version catalog: swagger = 2.2.50, swagger-annotations -> io.swagger.core.v3:swagger-annotations-jakarta; swagger-core alias removed (no code uses swagger-core classes). - New io.swagger.core.v3.swagger-gradle-plugin (2.2.50) configuration in pulsar-broker replicating the Maven build's `swagger` profile from branch-4.2: 7 ResolveTask instances with the same output file names, base info/servers from src/main/openapi/*.json, assembled by ./gradlew :pulsar-broker:swaggerDocs into build/docs with the flat + v2/ + v3/ layout published on pulsar.apache.org. The plugin's default javax resolver dependencies are overridden with swagger-jaxrs2-jakarta on the swaggerDeps configuration. - pulsar-docs-tools, pulsar-websocket and pulsar-client-tools now declare guava/commons-lang3 explicitly (previously leaked onto the compile classpath via swagger-core 1.x transitives). - io.kubernetes:client-java's transitive Swagger 1.x annotations are excluded everywhere: they are inert metadata on the generated k8s models (verified: no Methodref/Fieldref in any class, runtime jar has zero references) and the JVM ignores missing annotation types. - Shade include pattern updated to the io.swagger.core.v3 group; LICENSE.bin.txt entries updated (checkBinaryLicense passes for server and shell distributions). Verification: generated documents are operation-identical with the published 4.2.1 docs (597/597 operations across all 7 files, zero drift); every annotated resource class in the repo (broker, functions worker, websocket, proxy) resolves cleanly through the real scanner; map-valued responses render the same additionalProperties value schemas as 4.2.1. Assisted-by: Claude Code (Opus 4.8)
…ptions
Proofreads the summary/description strings of the OpenAPI (Swagger v3)
annotations and fixes only clear textual defects, preserving meaning:
- Spelling typos: "doesn't exit" -> "doesn't exist" (PersistentTopics,
Namespaces, rest Topics), "ect" -> "etc", "sre" -> "are",
"serviceconfiguration" -> "ServiceConfiguration", "Requested" ->
"Request" (worker stats), "BrokersBase admin apis" -> "Brokers admin
apis" (leaked base-class name).
- Missing spaces at string-concatenation boundaries, e.g.
"at thenamespace level", "tenant orsubscriber", "cluster.If
authorization", "thiscall", "will betrimmed", "C++'s[Boost]".
- Stray leading spaces in operation summaries (" Set retention...") and
doubled spaces mid-sentence.
- Missing closing parentheses, e.g. "(if instance-id is not provided,
the stats of all instances is returned" and the sink/source
parallelism and schema-type descriptions.
Awkward-but-correct grammar was intentionally left untouched. Generated
documents remain operation-identical with the published 4.2.1 docs
(597/597 operations).
Assisted-by: Claude Code (Opus 4.8)
Renames the aggregate OpenAPI documentation task in pulsar-broker from `swaggerDocs` to `generateOpenApiSpecs` and its output directory from build/docs to build/openapi: ./gradlew :pulsar-broker:generateOpenApiSpecs -> pulsar-broker/build/openapi/ The generated file names and the flat + v2/ + v3/ layout are unchanged. Assisted-by: Claude Code (Opus 4.8)
Fixes ungrammatical and garbled wording in the OpenAPI annotation
summary/description strings while preserving the technical meaning,
e.g.:
- "Get is enable sub type for specified topic" -> "Get the enabled
subscription types for the specified topic" (and the Set variant)
- "will let the broker removes all producers" -> "will make the broker
remove all producers"
- "Topic don't owner by this broker\!" -> "Topic is not owned by this
broker\!"; "Broker don't use MLTransactionMetadataStore\!" -> "Broker
doesn't use ..."; "This Broker is not enable transaction" -> "This
Broker does not have transactions enabled"
- "Partitioned topic already exist" -> "already exists"; "Expiry
messages" -> "Expire messages"; "An REST endpoint" -> "A REST
endpoint"; "The type of an value" -> "the type of a value";
"configurations's name" -> "configurations' names"
- "Requested should be executed by Monitoring agent" -> "The request
should be executed by the Monitoring agent"
- copy-paste leaks: Source endpoints saying "Pulsar Function
successfully created/updated" / "The function was successfully
deleted" now say "Pulsar Source"; setEntryFilters request body
description said "Enable sub types for the specified topic"
- '"advertisedListeners" must enabled in broker side' -> 'must be
enabled on the broker side'; missing spaces after punctuation
("schema.if", "**DISCARD**:silently", "earlier(no later)")
Candidates were located by spell-checking the descriptions rendered
into the generated OpenAPI documents and from the previous cleanup
pass's report. Generated documents remain operation-identical with the
published 4.2.1 docs (597/597 operations).
Assisted-by: Claude Code (Opus 4.8)
…pache#18947 ExtPersistentTopics/ExtNonPersistentTopics were a workaround (self-described as such in their javadoc) for apache#18947: the PartitionedTopicMetadata variant of createPartitionedTopic shares PUT /{tenant}/{namespace}/{topic}/partitions with the int variant, the OpenAPI specification forbids two operations on the same path and method, and the Swagger 1.x toolchain produced unstable output - so the methods were exiled into separate undocumented classes, whose implementations then drifted from the originals. At runtime Jersey disambiguates the two methods by content type (the metadata variant declares @consumes("application/vnd.partitioned-topic-metadata+json")), and OpenAPI 3 expresses exactly that: a single operation whose requestBody carries one schema per media type. With that representation the workaround classes are unnecessary: - The metadata overload moves into PersistentTopics next to the int overload, marked @operation(hidden = true) so the path is documented once; the visible operation's request body documents both content types (application/json -> integer, application/vnd.partitioned-topic-metadata+json -> PartitionedTopicMetadata with partitions and properties). - Both overloads share a new validateAndCreatePartitionedTopic helper. NonPersistentTopics overrides only this helper (non-persistent topics validate the topic name without the partitioned-name/policy checks), so the inherited metadata overload picks up the right validation via virtual dispatch and the duplicated, drifted method bodies are gone. - ExtPersistentTopics and ExtNonPersistentTopics are removed; PersistentTopicsTest now exercises the metadata variant through PersistentTopics. The /admin/v2 Jersey registration is package-based, so no registration changes are needed. Verified: PersistentTopicsTest passes (46/46, including the metadata-variant creation tests); two consecutive OpenAPI generation runs are byte-identical (the instability from apache#18947 is gone); the generated document remains operation-identical with the published 4.2.1 docs (521/521 admin v2 operations) while PUT .../partitions now documents both content types. Assisted-by: Claude Code (Opus 4.8)
merlimat
approved these changes
Jun 5, 2026
dao-jun
approved these changes
Jun 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18947
PIP: PIP-472 (follow-up: completes the Swagger migration that PIP-472 explicitly deferred)
Motivation
PIP-472 migrated Pulsar from
javax.*tojakarta.*but deferred the Swagger migration ("Swagger 1.x -> Swagger Core 2.x is deferred to a follow-up (decoupled, compile-/doc-only)"). Pulsar still depended on Swagger 1.6.2 (io.swagger:*), which is end-of-life, javax-era, and incompatible with the jakarta REST tier. In addition, OpenAPI document generation (theswagger.jsonfiles published as the REST API reference on pulsar.apache.org) had no equivalent in the Gradle build after the Maven removal.This PR migrates all Swagger annotations to Swagger Core v3 (
io.swagger.core.v3:swagger-annotations-jakarta:2.2.50, OpenAPI 3) and restores spec generation via the officialio.swagger.core.v3.swagger-gradle-plugin.Modifications
Annotation migration (61 Java files)
@Api->@Tag(+@Hiddenwhere v1 hadhidden=true),@ApiOperation->@Operation,@ApiResponse(code=, message=, response=, responseContainer=)->@ApiResponse(responseCode=, description=, content=)with@ArraySchema/additionalPropertiesSchemacontainer mappings,@ApiParam->@Parameter(bound params incl.@FormDataParammultipart parts) /@RequestBody(body params),@ApiModel/@ApiModelProperty->@SchemawithrequiredMode,@Example/@ExampleProperty->@ExampleObject.@Schema(type = "object", additionalPropertiesSchema = X.class)— the only form the swagger-core 2.2.50 resolver renders correctly; the@Content-leveladditionalPropertiesSchema = @Schema(implementation = X.class)form silently emits an empty value schema.@Schema(implementation=)replaced withtype = "object"schemas (Jackson introspection crashes on them; those endpoints emit protobuf JSON).BaseGenerateDocumentationreflection ported to@Schema; removed the unusedNoSwaggerDocumentationmarker and stale workaround comments for swagger-core#449 / swagger-ui#558 (both fixed upstream); nested map schemas are now expressed precisely.Resolution of #18947 ("Unstable swagger output") —
ExtPersistentTopics/ExtNonPersistentTopicsremovedPartitionedTopicMetadatavariant ofcreatePartitionedTopicsharesPUT /{tenant}/{namespace}/{topic}/partitionswith the int variant; the OpenAPI spec forbids two operations on the same path+method, which made the Swagger 1.x output unstable — so the methods had been exiled into separate, undocumentedExt*Topicsworkaround classes whose implementations then drifted.@Consumes("application/vnd.partitioned-topic-metadata+json")), and OpenAPI 3 expresses exactly that: one operation whose request body carries one schema per media type. The visiblecreatePartitionedTopicoperation now documents both bodies (application/json-> integer,application/vnd.partitioned-topic-metadata+json->PartitionedTopicMetadatawith partitions and properties).PersistentTopics(marked@Operation(hidden = true)so the path is documented once); both overloads share a newvalidateAndCreatePartitionedTopichelper, whichNonPersistentTopicsoverrides with its looser validation — the inherited metadata overload picks it up via virtual dispatch. TheExt*Topicsclasses are deleted (net -159 lines).Build
swagger = 2.2.50,swagger-annotations->io.swagger.core.v3:swagger-annotations-jakarta; removed the unusedswagger-corealias.io.swagger.core.v3.swagger-gradle-plugin(2.2.50) configuration inpulsar-brokerreplicating the Mavenswaggerprofile from branch-4.2: 7 resolve tasks with the same output file names and base info/servers, assembled by./gradlew :pulsar-broker:generateOpenApiSpecsintobuild/openapi/with the flat +v2/+v3/layout published on pulsar.apache.org. The plugin's default javax resolver dependencies are replaced withswagger-jaxrs2-jakartavia theswaggerDepsconfiguration.pulsar-docs-tools,pulsar-websocketandpulsar-client-toolsnow declare guava/commons-lang3 explicitly (previously leaked onto the compile classpath via swagger-core 1.x transitives).io.kubernetes:client-java's transitive Swagger 1.x annotations are excluded: they are inert metadata on the generated k8s models (verified — noMethodref/Fieldreftoio.swaggerin any class of either jar; the JVM ignores missing annotation types during reflection). The k8s-exercising test suites pass with the exclusion.io.swagger.core.v3group;LICENSE.bin.txtupdated for both distributions.Verifying this change
This change is already covered by existing tests and verified as follows:
additionalPropertiesvalue schemas as 4.2.1.@Hiddenclasses excluded by design, matching 1.x behavior).checkBinaryLicensepasses for the server and shell distributions; fullcompileTestJava, checkstyle, spotless andpulsar-docs-toolstests pass; the kubernetes-client-dependent test suites (pulsar-functions-runtime,pulsar-functions-secrets,pulsar-broker-auth-oidc) pass with the swagger-annotations exclusion.PersistentTopicsTestpasses (46/46, including the metadata-variant creation tests, now routed throughPersistentTopics); two consecutive OpenAPI generation runs produce byte-identical output, confirming the reported instability is gone.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
Dependency changes:
io.swagger:swagger-annotations/swagger-core 1.6.2removed;io.swagger.core.v3:swagger-annotations-jakarta 2.2.50added (annotations only, no transitive deps);io.swagger:swagger-annotationsexcluded fromio.kubernetes:client-java. REST endpoint behavior is unchanged — only the documentation annotations on the resource classes changed. As noted in PIP-472's compatibility section, authors of plugins that contribute JAX-RS resources using Swagger 1.x annotations need to migrate toio.swagger.v3.oas.annotations.*when recompiling against this version.Assisted-by: Claude Code (Opus 4.8)