Swagger Core 3

When upgrading to Swagger Core 3, refer to the Swagger Core 3.0 Migration Guide for the breaking changes, including the move from Jackson 2 to Jackson 3.

The io.arconia.rewrite.openapi.swagger.UpgradeSwaggerCore_3 recipe automates most of the changes introduced in Swagger Core 3.x. You can use it to update your project to the latest Swagger Core 3.x release.

Prerequisites

Swagger Core 3 supports only the jakarta namespace. The recipe migrates a project only when its code uses none of the javax REST, Servlet, Bean Validation, or JAXB APIs and it doesn’t declare the javax variant of the Swagger REST artifacts (swagger-jaxrs2, swagger-jaxrs2-servlet-initializer, and swagger-jaxrs2-servlet-initializer-v2). Otherwise, it leaves the project unchanged, so it is never left half migrated. In a multi-module build, each module is checked on its own.

If your project still uses the javax namespace, migrate it to Jakarta EE first, then run this recipe.

What the Recipe Changes

The recipe will:

  • Replace the retired Swagger Core artifacts with their 3.x counterparts: swagger-annotations-jakarta, swagger-models-jakarta, swagger-core-jakarta, and swagger-integration-jakarta drop the -jakarta suffix, swagger-jaxrs2-jakarta becomes swagger-rest, and both swagger-jaxrs2-servlet-initializer-v2-jakarta and its deprecated predecessor swagger-jaxrs2-servlet-initializer-jakarta become swagger-rest-servlet-initializer.

  • Upgrade all io.swagger.core.v3 dependencies, the Swagger Gradle plugin, and the Swagger Maven plugin to the 3.x release line, replacing the retired swagger-maven-plugin-jakarta with swagger-maven-plugin.

  • Remove the swagger-java17-support dependency, which is no longer published.

  • Declare the commons-lang3 dependency when your code uses it, since Swagger Core no longer brings it in transitively.

  • Upgrade the Jakarta REST API to 4.0 and the Jakarta Servlet API to 6.1, as required by Jakarta EE 11.

  • Upgrade Jersey to 4.x, which implements Jakarta REST 4.0, and replace jersey-media-json-jackson with jersey-media-json-jackson3, so that your resources keep serializing JSON through Jackson 3.

  • Move the REST integration from the io.swagger.v3.jaxrs2 packages to io.swagger.v3.rest, and rename the Jaxrs-prefixed types to their Rest counterparts (for example, JaxrsOpenApiContextBuilder becomes RestOpenApiContextBuilder). The recipe updates these names in Java code, in string literals (such as a scanner class passed to SwaggerConfiguration), in XML files (such as the servlet declarations in web.xml), in module-info.java, and in the META-INF/services descriptor of a custom OpenAPIExtension.

  • Change the ObjectMapperProcessor implementations to return the mapper they receive, and remove the deprecated processYamlObjectMapper method, which Swagger Core no longer calls.

  • Migrate the project to Jackson 3, moving the Jackson dependencies and imports from com.fasterxml.jackson to tools.jackson. The Jackson annotations keep their com.fasterxml.jackson.annotation package.

See its entry in the Recipe Catalog for the full list of recipes this upgrade applies, each with its options and a before/after example.

Manual Follow-Up

Some changes need a decision or can’t be made safely by a recipe:

  • Mapper defaults. Swagger Core 3 builds its mappers from the Jackson 3 defaults, which change the generated output. For example, java.time.Month values are written as numbers, an accessor such as getURL() produces a URL property instead of url, and a constructor parameter whose name matches no getter or field becomes an extra writeOnly schema property. Compare the generated OpenAPI document with the previous one. To restore most of the Jackson 2 behavior, call ObjectMapperFactory.setJackson2Compatibility(true) at startup, before the first mapper is used.

  • Mappers configured in place. Jackson 3 mappers are immutable. An ObjectMapperProcessor method that configured the mapper it received, such as with mapper.enable(…​), must now return a new one built with mapper.rebuild().

  • Mappers created outside Swagger Core. A mapper that isn’t built by ObjectMapperFactory drops properties such as $ref from the Swagger models. Serialize and deserialize them with Json.mapper(), Json31.mapper(), or ObjectMapperFactory.buildStrictGenericObjectMapper().

  • SwaggerAnnotationIntrospector subclasses. Like their Jackson 3 counterparts, the hasRequiredMarker, findPropertyDescription, findSubtypes, and findTypeName methods take a MapperConfig<?> first parameter. Update the overrides and keep @Override on them: without it, an override with the old signature still compiles but is never called.

  • Cached mappers. A component that keeps the result of Json.mapper() or Yaml.mapper() from startup doesn’t see customizers registered later with ObjectMapperFactory.addCustomizer(…​). Get the mapper when you use it instead.

  • Configuration files. Class names in Swagger configuration files, such as a scannerClass or readerClass in openapi-configuration.yaml, are not updated.

  • Jersey JSON support. If your Jersey application didn’t use jersey-media-json-jackson before, add org.glassfish.jersey.media:jersey-media-json-jackson3 so that your resources can read and write JSON.

  • Libraries built for Swagger Core 2. A third-party library compiled against Swagger Core 2.x needs a release compatible with Swagger Core 3. This includes a library that brings in the javax variant of the Swagger REST artifacts, such as swagger-jaxrs2: upgrade the library or exclude the artifact.

After running the recipe, check that the resolved runtime classpath contains only Swagger Core 3.x artifacts.

Run the Recipe

You can apply this recipe with any OpenRewrite-compatible tool. The tabs below show three of them: the Arconia CLI, the OpenRewrite Gradle plugin, and the OpenRewrite Maven plugin. See Running Recipes for a comparison of when to pick each one.

  • Arconia CLI

  • Gradle

  • Maven

arconia rewrite run --recipe-name io.arconia.rewrite.openapi.swagger.UpgradeSwaggerCore_3

First, create an init.gradle file in your project root with the following content.

initscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath("org.openrewrite:plugin:latest.release")
    }
}

rootProject {
    plugins.apply(org.openrewrite.gradle.RewritePlugin)
    dependencies {
        rewrite("io.arconia.migrations:rewrite-openapi:latest.release")
    }

    // Remove when project repositories are disabled
    afterEvaluate {
        if (repositories.isEmpty()) {
            repositories {
                mavenCentral()
            }
        }
    }

    configurations.named("rewrite") {
        canBeConsumed = false
    }
}

Then, run the following command.

./gradlew rewriteRun \
    --init-script init.gradle \
    --no-parallel \
    -DactiveRecipe=io.arconia.rewrite.openapi.swagger.UpgradeSwaggerCore_3

You can remove the init.gradle file afterwards.

./mvnw -U org.openrewrite.maven:rewrite-maven-plugin:run \
  -Drewrite.recipeArtifactCoordinates=io.arconia.migrations:rewrite-openapi:LATEST \
  -Drewrite.activeRecipes=io.arconia.rewrite.openapi.swagger.UpgradeSwaggerCore_3