Tobias Erdle's Blog

Writes mostly about Java, Jakarta EE and software development in general. All views are my own.

Github: erdlet | E-Mail: blog@erdlet.info

Generate Jakarta RESTful Web Services using jakarta namespace with Swagger

TL;DR

At the time of writing, I couldn't find a Swagger generator for Swagger Codegen 3.X which supports the jakarta.* namespace when I want to use jaxrs-di to generate the API classes specified before. To solve this issue, I implemented workaround utilizing the io.github.floverfelt:find-and-replace-maven-plugin to search for javax and replace it with jakarta. Afterwards, the classes can be used like inteded.

The situation

I was curious if I can use Swagger for generating my Jakarta REST API in the meanwhile. As the jakarta.* namespace was release about two years ago, I thought there will be a generator for those classes. But unfortunately, this is not the case at the time of writing this post. When you generate the API classes with the latest io.swagger.codegen.v3:swagger-codegen-maven-plugin:3.0.34 Maven plugin, there are still the javax.* imports generated. As a first step, I started to have a look into the issue tracker of_Swagger Codegen searching for the term jakarta. And there was an issue, but there's no reaction since a few months. In the Swagger Codegen Generators issue tracker there's even less information, because there are exactly zero issues targeting this topic (except from the one I opened). So from Swagger side there seems to be no real interest in this topic, so I had to start to find a custom solution.

The solution

I thought about how to achieve that the generated code is post-processed in the build process. What I needed was a Maven plugin that replaces tokens after the Swagger generator did its job. After doing a little DuckDuckGo research, I stumbled over the old Google Replacer Plugin and a newer project targeting the same topic: the find-and-replace-maven-plugin. To be honest, I didn't test this in depth, because the only thing I wanted was to replace the word javax with jakarta and no hardcore regex magic. So I configured my build like this. The buildhelper-maven-plugin is just used to add a source folder in which the generated classes are put to the Maven output.

<build>
<!-- ... -->
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <version>3.2.0</version>
        <executions>
            <execution>
                <id>add-source</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>add-source</goal>
                </goals>
                <configuration>
                    <sources>
                        <source>src/generated/java/</source>
                    </sources>
                </configuration>
            </execution>
        </executions>
    </plugin>

    <plugin>
        <groupId>io.swagger.codegen.v3</groupId>
        <artifactId>swagger-codegen-maven-plugin</artifactId>
        <version>3.0.34</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                    <inputSpec>${project.basedir}/openapi/api.yml</inputSpec>
                    <language>jaxrs-di</language>
                    <output>${project.basedir}</output>
                    <generateSupportingFiles>false</generateSupportingFiles>
                    <addCompileSourceRoot>false</addCompileSourceRoot>
                    <generateApiDocumentation>false</generateApiDocumentation>
                    <apiPackage>de.erdlet.demos.jakartarestopenapidemo.api</apiPackage>
                    <modelPackage>de.erdlet.demos.jakartarestopenapidemo.api.model</modelPackage>
                    <modelNameSuffix>ApiModel</modelNameSuffix>
                    <configOptions>
                        <sourceFolder>src/generated/java</sourceFolder>
                        <dateLibrary>java8</dateLibrary>
                    </configOptions>
                </configuration>
            </execution>
        </executions>
    </plugin>

    <!-- WORKAROUND since there is no Swagger language support for jakarta.* namespace -->
    <plugin>
        <groupId>io.github.floverfelt</groupId>
        <artifactId>find-and-replace-maven-plugin</artifactId>
        <version>1.1.0</version>
        <executions>
            <execution>
                <id>exec</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>find-and-replace</goal>
                </goals>
                <configuration>
                    <replacementType>file-contents</replacementType>
                    <baseDir>src/generated/java/</baseDir>
                    <findRegex>javax</findRegex>
                    <replaceValue>jakarta</replaceValue>
                    <recursive>true</recursive>
                </configuration>
            </execution>
        </executions>
    </plugin>
<!-- ... -->
</build>

Now when I generate the API classes, they are generated with the javax namespace first, put into src/generated/java and afterwards processed by the find-and-replace-maven-plugin. Please be careful about the order of the swagger-codegen-maven-plugin and find-and-replace-maven-plugin. Because they run in the same phase, they're executed in the order they're placed inside the pom.

To have an example, this class is the result of the example API specification of the GitHub repo linked below:

package de.erdlet.examples.swaggerjakartaee9example.api;

import de.erdlet.examples.swaggerjakartaee9example.api.model.ErrorApiModel;
import de.erdlet.examples.swaggerjakartaee9example.api.model.TodoApiModel;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;

@Path("/todos")


@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaJerseyDIServerCodegen", date = "2022-08-14T13:29:04.673325517+02:00[Europe/Berlin]")public class TodosApi  {

   private TodosApiService delegate;

   protected TodosApi() {
   }

   @jakarta.inject.Inject
   public TodosApi(TodosApiService delegate) {
      this.delegate = delegate;
   }

   // ...
}

I allowed myself to remove uninteresting code to make the example more concise. But as you can see, there are no javax.* dependencies left and the code compiles, assuming the necessary dependencies are declared. Now I can use Swagger with Jakarta EE 9 and newer versions.

But to be honest, I'm really disappointed that there is still such less interest in supporting the new namespace, even though it is the future of all specifications under the Jakarta EE umbrella and even Spring is migrating to the jakarta.* namespace. Maybe, and that's my hope, we'll have at least at this time a Swagger generator supporting the current technology and not only the past.

Further resources