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:
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.
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?
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!
Please create an example for Gradle..
[…] 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. […]
Thank you Daniel for the tutorial. Unfortunately it looks that ‘openapi-generator-maven-plugin’ (version 5.4.0) doesn’t support url in the ”, just file: https://github.com/OpenAPITools/openapi-generator/issues/2241. So I added also ‘springdoc-openapi-maven-plugin’ (doc: https://springdoc.org/v2) to generate OpenAPI description json file beforhand.
[…] the documentation and Maven as your build automation tool. If you are not using Springdoc OpenAPI, click here to read about generating client code from Spring Boot using Maven for a slightly less efficient approach that works with other OpenAPI […]