Generate client code from Spring Boot using Maven

Generating client code from an OpenAPI specification can save a lot of development time and reduce risk of that code being outdated. However, it is not immediately obvious how to generate that code from a Spring Boot application. This article explains how to generate Angular code from a Java Spring Boot project using Springdoc Swagger and Maven (though you can easily swap out Angular for any other language).

This solution has been tested to work with both Springfox Swagger (OpenAPI 2.0) and Springdoc OpenAPI (OpenAPI 3.0). It is also likely to work with other automated documentation libraries, as long as they provide an endpoint containing the OpenAPI specification for the application.

Your project should be using Maven as your build automation tool. If you are using Gradle, however, you might have luck applying a similar configuration using the Spring Boot Gradle Plugin and the OpenAPI Generator Gradle Plugin.

Alternatively, you may find use in combining the GenerateSwagger test class from my previous article on the subject with either the Swagger Codegen CLI, the OpenAPI Generator CLI, instead.

To follow along with this article, I encourage you to use the example project. The following commit provides the blank slate we need:

https://github.com/daniel-frak/openapi-client-code-generation/tree/812d215c8d9bcd04d4f3fac422ca00ec32bfe96e

Add POM configuration

To achieve our goal we will create a new Maven profile in which we will add the openapi-generator-maven-plugin, as well as override configuration for the the existing spring-boot-maven-plugin.

Spring-boot-maven-plugin will be used to temporarily launch our application through maven, while openapi-generator-maven-plugin will retrieve the OpenAPI specification from its endpoint to generate the desired Angular code.

The code generation will be made to execute during the integration-test phase in a custom profile, so that we can run it by invoking Maven’s verify phase (invoking just integration-test will not work as we will also need the pre- and post- phases).

The entire profile configuration is as follows:

<!-- Client code generation -->
<profiles>
    <profile>
        <id>angular</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>pre-integration-test</id>
                            <goals>
                                <goal>start</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>post-integration-test</id>
                            <goals>
                                <goal>stop</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.openapitools</groupId>
                    <artifactId>openapi-generator-maven-plugin</artifactId>
                    <version>4.2.2</version>
                    <executions>
                        <execution>
                            <id>angular-client-code-generation</id>
                            <phase>integration-test</phase>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                            <configuration>
                                <inputSpec>http://localhost:8080/v3/api-docs</inputSpec>
                                <output>${project.build.directory}/generated-sources/angular</output>

                                <generatorName>typescript-angular</generatorName>
                                <!--
                                    Use this option to dump the configuration help for the specified generator
                                    instead of generating sources:
                                    <configHelp>true</configHelp>
                                -->

                                <configOptions>
                                    <!--
                                        Put generator-specific parameters here, e.g. for typescript-angular:
                                        <apiModulePrefix>Backend</apiModulePrefix>
                                     -->
                                </configOptions>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Spring-boot-maven-plugin is overriden, as we must make sure that our application is running when the openapi-generator-maven-plugin‘s goal is executed. That way, the application will be started before the integration-test phase begins and stopped after it finishes.

Setting up openapi-generator-maven-plugin itself, however, is a little bit more complicated. Firstly, this plugin’s default phase is generate-sources, which means that it would run before the specification file is available. That is why the phase is explicitly set as integration-test.

Inside the <configuration> tag, <inputSpec> is used to define the location of the specification file. Make sure that it correctly points to your application’s OpenAPI specification endpoint, which will usually be:

  • http://localhost:8080/v2/api-docs – for OpenAPI 2.0
  • http://localhost:8080/v3/api-docs – for OpenAPI 3.0

<output> represents the target folder of the generated client code. Feel free to change it if the default is not satisfactory. You could potentially even point it straight to your frontend application folder.

<generatorName> indicates which generator to use for the code generation. In this case typescript-angular is used, as that will create the necessary Angular 2+ files.

Optionally, generator-specific parameters can be specified inside <configOptions>.

Generate client code

The client code can now be generated using a simple mvn command:

mvn clean verify -P angular

This will build the application and generate the Angular code in /target/generated-sources/angular.

Advanced configuration (optional)

Right now the application runs on its default port. If one is already running in the background, Maven will fail with a “this port is already in use” error. The configuration below will choose a random unused port to make sure this never happens:

<!-- Client code generation -->
<profiles>
    <profile>
        <id>angular</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>reserve-tomcat-port</id>
                            <goals>
                                <goal>reserve-network-port</goal>
                            </goals>
                            <phase>process-resources</phase>
                            <configuration>
                                <portNames>
                                    <portName>tomcat.http.port</portName>
                                </portNames>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>pre-integration-test</id>
                            <goals>
                                <goal>start</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>post-integration-test</id>
                            <goals>
                                <goal>stop</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <arguments>
                            <argument>--server.port=${tomcat.http.port}</argument>
                        </arguments>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.openapitools</groupId>
                    <artifactId>openapi-generator-maven-plugin</artifactId>
                    <version>4.2.2</version>
                    <executions>
                        <execution>
                            <id>angular-client-code-generation</id>
                            <phase>integration-test</phase>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                            <configuration>
                                <inputSpec>http://localhost:${tomcat.http.port}/v3/api-docs</inputSpec>
                                <output>${project.build.directory}/generated-sources/angular</output>

                                <generatorName>typescript-angular</generatorName>
                                <!--
                                    Use this option to dump the configuration help for the specified generator
                                    instead of generating sources:
                                    <configHelp>true</configHelp>
                                -->

                                <configOptions>
                                    <!--
                                        Put generator-specific parameters here, e.g. for typescript-angular:
                                        <apiModulePrefix>Backend</apiModulePrefix>
                                     -->
                                </configOptions>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Remember, however, that you will most likely need to provide a custom base path for your server. Otherwise, the random port will be assumed correct as part of the specification. Here is an example of a fix for OpenAPI 3.0:

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
import org.springframework.context.annotation.Configuration;

@OpenAPIDefinition(servers = {
        @Server(url = "http://localhost:8080")
})
@Configuration
public class SwaggerConfig {}

Conclusion

The work done in this post (sans the advanced configuration) is contained in the commit 610cdaa0c626bebd4d75fc7cb187697106df3a9f.

This is the easiest way to generate client code from an existing Spring Boot application, while not adding any performance overhead in existing build pipelines. If code must be generated for more clients, additional profiles can be created, each for a specific generator and configuration.

Daniel Frąk Written by:

Be First to Comment

    Leave a Reply

    Your email address will not be published. Required fields are marked *