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 (potentially breaking Swagger UI). Here is an example OpenAPI 3.0 configuration which takes this into account:

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import java.util.Arrays;
import java.util.Collections;

@Configuration
@OpenAPIDefinition
public class SwaggerConfig {

    private static final String CODE_GENERATION_PROFILE = "angular";

    private final Environment environment;

    SwaggerConfig(Environment environment) {
        this.environment = environment;
    }

    @Bean
    public OpenAPI customOpenAPI() {
        var openApi = new OpenAPI()
                .info(getInfo());

        if (Arrays.asList(environment.getActiveProfiles()).contains(CODE_GENERATION_PROFILE)) {
            var server = getLocalhostServer();
            openApi.setServers(Collections.singletonList(server));
        }

        return openApi;
    }

    private Info getInfo(OpenApiProperties properties) {
        return new Info()
                .title("Project title goes here")
                .description("Project description goes here")
                .version("Project version goes here");
    }

    private Server getLocalhostServer() {
        var server = new Server();
        server.setUrl("http://localhost:8080");
        return server;
    }
}

In this case, you must also make sure that Spring Boot starts with the angular profile, for example by modifying the previously mentioned spring-boot-maven-plugin configuration:

<profiles>
    <!-- ... -->
    <profile>
        <id>angular</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <!-- ... -->
                    <configuration>
                        <!-- Add this: -->
                        <profiles>
                            <profile>angular</profile>
                        </profiles>
                        <!-- ... -->
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

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:

6 Comments

  1. Stefan
    January 19, 2020
    Reply

    Thanks for this tutorial!

    Unfortunately I am stuck right after generating the code.

    The problem: I cannot build the client. I am getting a README.md with instructions which cannot fulfilled.

    E.g. it starts off by stating one has to run “npm install” and “npm run build” which won’t work because there is neither a package.json file nor a “build” script.

    It looks like the generated code is not yet usable?

    • Daniel Frąk
      January 21, 2020
      Reply

      Hi Stefan,
      It seems you are correct and the generated code does lack a package.json file, which might be a bug in the typescript-angular generator!
      The generated code, however, should still be usable as an Angular module – just copy the files over to your Angular application (e.g. to ‘src/backend’ or ‘src/api’) and import its ApiModule in your app.module.ts file (you will also need to import HttpClientModule). Then you should be able to inject the generated services into your Angular components.

      Let me know if this helped!

  2. Stefan
    January 8, 2021
    Reply

    Please create an example for Gradle..

  3. […] The springdoc-openapi library allows us to automatically generate an OpenAPI specification for our rest API built with Spring Boot. This specification is also useful when we need a Swagger documentation or we want to automate client code generation. […]

Leave a Reply

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