OpenAPI-based Code Generation
Overview
The OpenAPI specification provides a standard way to express RESTful APIs.
Separately, the OpenAPI generator project has created a powerful code generator tool which accepts an OpenAPI document and generates client and server code for many languages and frameworks. The Helidon team contributes to this tool to ensure that it provides strong support for Helidon SE clients and servers. As a result, you can use the generator to create code that fits smoothly into your Helidon applications.
Use the OpenAPI generator release 7.6.0 or later which this document describes.
In the vocabulary of the tool, there are two generators for Helidon:
java-helidon-client(hereafter the Helidon client generator)java-helidon-server(hereafter the Helidon server generator).
Each of these generators supports two libraries:
mp- for Helidon MP code generationse- for Helidon SE code generation
Use the Helidon client generator and its se library to create a Helidon SE
client based on Helidon WebClients. The resulting
client library works with any server that implements the API declared in the
OpenAPI document you specified when you ran the generator. The client library
provides an abstraction similar to remote procedure calls (RPC). To access a
remote service that implements the endpoints declared in the OpenAPI document,
your code uses the generated client library first to establish a connection to
the remote service and then to call remote service endpoints by invoking local
methods passing POJO business objects or Java types as arguments.
Use the tool’s Helidon server generator and its se library to create server
endpoint stubs for a Helidon SE service. You build on these stubs by extending a
generated class or implementing a generated interface, adding your specific
business logic to finish the implementation of the endpoints. The combination of
the generated server code plus Helidon SE underneath it allows you to focus on
the business details instead of resource boilerplate.
You can run the OpenAPI generators in three ways:
- using the OpenAPI generator CLI
- using the OpenAPI generator Maven plug-in
- using the online OpenAPI generator website
The rest of this document walks you through how to use each technique and how to configure the generators to produce the code you want.
Maven Coordinates
Your project does not need any dependencies on the OpenAPI generator.
To use the OpenAPI generator plug-in to generate or regenerate files during your
project build, add the following to your project’s pom.xml file to declare the
plug-in. Choose whichever version of the generator plug-in meets your needs as
long as it is at least 7.6.0.
Declaring the OpenAPI Generator Plug-in:
<properties>
<openapi-generator-version>7.6.0</openapi-generator-version>
</properties>
<!-- ... -->
<build>
<!-- ... -->
<plugin-management>
<!-- ... -->
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-version}</version>
</plugin>
<!-- ... -->
</plugin-management>
<!-- ... -->
</build>
A later section describes how to invoke the plug-in during your build.
Configuration
The OpenAPI generators support a substantial, powerful, and sometimes bewildering group of configuration settings. For complete lists see these pages:
The OpenAPI generator loosely divides its settings into three types:
- global properties
These settings generally govern the overall behavior of the tool, regardless of which specific generator you use.
For the CLI, use the common option style:-i petstore.yaml--input-spec petstore.yaml
For the Maven plug-in, use elements within the<configuration>section of the plug-in:<configuration> <inputSpec>petstore.yaml</inputSpec> </configuration> - options
These settings typically affect how particular generators operate.
For the CLI, specify config options as additional properties:--additional-properties=groupId=com.mycompany.test,artifactId=my-example
orTerminal-p groupId=com.mycompany.test -p artifactId=my-example
For the Maven plug-in, use the<configOptions>section within<configuration>:pom.xml<configuration> <!-- ... --> <configOptions> <groupId>com.mycompany.test</groupId> <artifactId>my-example</artifactId> </configOptions> <!-- ... --> </configuration> - additional properties
Settings in this category typically are passed to the templates used in generating the files, although generators can use additional properties in deciding how to generate the files.
For the CLI:--additional-properties "useAbstractClasses=false,returnResponse=true"
orTerminal-p useAbstractClasses=false -p returnResponse=true
For the Maven plug-in, use an<additionalProperties>section within the<configuration>section for the plug-in:<configuration> <!-- ... --> <additionalProperties> <additionalProperty>useAbstractClasses=false</additionalProperty> <additionalProperty>returnResponse=true</additionalProperty> </additionalProperties> </configuration>
Keep this distinction among global options, config options, and additional properties in mind so you know how to express the configuration you want. The earlier links to the lists of configuration options for the Helidon generators groups options and additional properties in separate tables.
The next few sections describe, in turn, required settings, settings we recommend, and other common settings most developers will want to use.
Required Settings
You must specify the following options:
| Option | Short Option | Plug-in Setting | Description | Values |
|---|---|---|---|---|
--inputSpec | -i | <inputSpec> | Path to the OpenAPI document defining the REST API | |
--generatorName | -g | <generatorName> | Generator you want to use (java-helidon-server or java-helidon-client) | java-helidon-serverjava-helidon-client |
--library | <library> | Library you want to use | mpse |
Recommended Settings for the OpenAPI Generator
Your project might have different needs, but in general we advise developers to use the following settings when using the OpenAPI generator, both from the command line and using the Maven plug-in.
Recommended OpenAPI Generator Additional Properties:
| Property | Description | Default |
|---|---|---|
apiPackage | Name of the package for generated API interfaces/classes | org.openapitools.server.api ororg.openapitools.client.api |
modelPackage | Name of the package for generated model (POJO) classes | org.openapitools.server.model ororg.openapitools.client.model |
invokerPackage | Name of the package for generated driver classes | org.openapitools.server ororg.openapitools.client |
groupId | Group ID in the generated pom.xml | org.openapitools |
artifactId | Artifact ID in the generated pom.xml | openapi-java-server oropenapi-java-client |
artifactVersion | Artifact version in the generated pom.xml | 1.0.0 |
useAbstractClass | Generate server abstract classes instead of interfaces. Setting to true generates significantly more helpful code. | false |
Recommended OpenAPI Generator Plug-in Options:
| Plug-in Option | Description | Default |
|---|---|---|
<output> | Directory where the generator should place files. + We strongly recommend <output>target/generated-sources</output> or a subdirectory below there. | .(current directory) |
<addCompileSourceRoot> | Whether Maven should include the output directory as a source root (that is, include it automatically in the build). + We advise <addCompileSourceRoot>true</addCompileSourceRoot>. | false |
Common Settings
Among the many configuration settings available to you, some you should particularly consider are summarized in the table below. Refer to the earlier links for complete lists.
Common OpenAPI Generator Additional Properties:
| Property | Description | Values | Default | Notes |
|---|---|---|---|---|
helidon | Version of Helidon for which to generate the files | Latest published Helidon release * |
Affects the APIs which are used, Helidon 3.x vs Helidon 4.x or javax vs. jakarta | |
full | Whether to generate all the normal files or only API files | true | false | The "API files" include files developers do not normally modify after they are generated: the interfaces or classes for the declared API and the model classes. |
false | ||||
serialization | Which Java library to use for serializing JSON | jsonb | jackson | |
jackson |
- The generator attempts to retrieve the list of released Helidon versions from the Helidon website, falling back to locally-stored Java preferences loaded from the previous generator run, and as a last resort using hard-coded values for each major Helidon release.
Usage
This section covers two major topics:
Planning Your Use of the OpenAPI Generators
Beyond the settings listed above, there are several important choices you need to make when planning your project and when running the OpenAPI generators. This section addresses those choices.
Generating a New Project and Generating Into an Existing Project
You can use the OpenAPI generator to create a new project or to generate files into an existing project. Some developers do both, using the generator to create the project at first and then to update the project as they evolve the OpenAPI document or change the generation options they select. Others create the project in some other way for example, using the Helidon CLI. The OpenAPI generator CLI and plug-in both support each type of usage.
If the OpenAPI generator finds a pre-existing API or model file, it overwrites
it with the latest content. It does not overwrite a pom.xml file or test
files. This is important because certain generation settings can influence the
generated dependencies in the pom.xml file. For example, the
serializationLibrary setting creates dependencies on either JSON-B or Jackson
artifacts. As a result, changing the generation options can change the
dependencies your project should have. If you rerun the generator, the old
pom.xml remains and does not reflect the revised depencencies.
As a practical matter, many developers use the OpenAPI generators in one of the following ways:
- Use the generator CLI once to create a new project.
By default, the generator CLI creates files in the normal Maven project structure:src/main/java, etc. Then you add your own files to that same project structure. Because the generated files are in the standard places, the project build includes them by default.You can run the generator CLI again to update the generated files. Because this happens outside the project’s build lifecycle, you need to remember to rerun the CLI yourself when you change the OpenAPI document.
You also need to identify and manually remove any previously-generated files that become obsolete. Similarly, you must understand how changes in the OpenAPI document or the generation options affect the project dependencies and update the projectpom.xmlaccordingly. - Use the generator plug-in to (re)generate files during each build.
Specify in the plug-in configuration that the generated files should reside intarget/generated-sourcesdirectory (the conventional location for generated sources) or a subdirectory below there. Each project build runs the OpenAPI generator which reads the then-current OpenAPI document file. With the generated files undertarget, you can usemvn cleanto remove any obsolete generated files left over from previous builds.In particular, withmvn cleaneach build regenerates the candidatepom.xmlundertarget/generated-sources. You can inspect the generatedpom.xmlfile for changes in dependencies and make any necessary changes in the actual projectpom.xmlfile.
Generating Interfaces or Classes
As you generate a Helidon SE server, you can choose whether you want Java interfaces or classes to represent the RESTful API endpoints.
By default, the Helidon OpenAPI server generator creates classes. You write your own concrete subclasses which extend those generated classes, supplying the business logic for each REST endpoint. Do not modify the generated classes.
If you set useAbstractClasses=false then the generator creates Java interfaces
instead of classes. You then write classes which implement those generated
interfaces.
Either way, you can safely regenerate the code later so long as you have not edited the generated code. The generator replaces the generated classes or interfaces but does not touch other classes you wrote.
The Helidon client generator always creates concrete classes. Typically, you do not need to customize the behavior in the generated client API classes. If you choose to do so, write your own subclass of the generated client API class; do not modify the generated files.
Grouping Operations into APIs
Each operation in an OpenAPI document can have a tags attribute. By default,
the generators group operations with the same tags value into the same API or
service. Alternatively, if you specify the option x-helidon-groupBy as
first-path-segment, the generators use the first segment of the path to group
operations together.
When you generate a Helidon SE server, the generator creates a separate interface or class for each API your service exposes. You implement each interface or extend each class to add your business logic for that API.
The generator creates Helidon routing logic based on the longest common path prefix shared among the operations that are grouped into each API.
TODO comment in the generated routing.Review the paths and the tags settings in your OpenAPI document and consider
revising one or the other so all operations in each API share a common path
prefix. If you do not have control over the OpenAPI document or do not want to
change it, consider specifying the generator option x-helidon-groupBy first-path-segment which groups operations into APIs not by tags value but
by the first segment of each operation’s path.
When you generate a Helidon SE client, the generated code contains a separate API class for each distinct API your code might invoke.
Running the OpenAPI Generators
Earlier we listed the ways you can run the OpenAPI generator:
- using the OpenAPI generator CLI
- using the OpenAPI generator Maven plug-in
- using the online OpenAPI generator website
The next sections describe each of these techniques in detail.
Using the OpenAPI Generator CLI
.jar file before you can run the CLI. Follow
these instructions and remember where you save the .jar
file. The examples below use the placeholder path-to-generator to represent
the directory where you store that downloaded file.The following example uses the Helidon server generator to create a project or regenerate files into an existing project.
Creating or updating a server project using the OpenAPI generator CLI:
java -jar ${path-to-generator}/openapi-generator-cli.jar \
generate \
-i src/main/resources/petstore.yaml \
-g java-helidon-server \
--library se \
-p groupId=io.helidon.examples \
-p artifactId=helidon-openapigen-se-server \
-p artifactVersion=1.0.0-SNAPSHOT \
-p apiPackage=io.helidon.examples.openapigen.se.server.api \
-p modelPackage=io.helidon.examples.openapigen.se.server.model \
-p invokerPackage=io.helidon.examples.openapigen.se.server
The next example runs the Helidon client generator using the same input file.
Creating or updating a client project using the OpenAPI generator CLI:
java -jar ${path-to-generator}/openapi-generator-cli.jar \
generate \
-i src/main/resources/petstore.yaml \
-g java-helidon-client \
--library se \
-p groupId=io.helidon.examples \
-p artifactId=helidon-openapigen-se-client \
-p artifactVersion=1.0.0-SNAPSHOT \
-p apiPackage=io.helidon.examples.openapigen.se.client.api \
-p modelPackage=io.helidon.examples.openapigen.se.client.model \
-p invokerPackage=io.helidon.examples.openapigen.se.client
The key differences between the commands are:
- the generator selected by the
-goption (clientvs.server), - the artifact ID and package names (
clientvs.server).
You could use these two commands together to generate a server submodule and a
client submodule in a pre-existing multi-module Maven project. Remember that the
resulting client project can access any server which implements the API
described in the petstore.yaml OpenAPI document, whether it was generated
using the OpenAPI generator tool or not.
In both examples, the generator creates the entire project if it does not exist
and recreates the generated API and model files if the project already exists.
The generator does not overwrite an existing pom.xml file,
previously-generated test files, or files you create yourself.
Invoking the OpenAPI Generator Maven Plug-in
You can run the OpenAPI generator plug-in as part of your project build to generate or regenerate files.
First, declare the plug-in as explained in the earlier section on Maven coordinates.
Then, in the <build> section of your pom.xml file, add an execution of the
plug-in with the configuration you want. By default, the plug-in runs during the
generate-sources phase of the Maven build.
The plug-in execution in the following example is equivalent to the CLI example above for generating server files:
Creating or updating a client project using the OpenAPI Maven plug-in:
Using the Online Generator
The OpenAPI tools project hosts and maintains the online OpenAPI generator at http://api.openapi-generator.tech. You can use the site’s API browser to explore the available generators and the settings each supports, expressed as JSON.
To generate your project, you supply the options and additional properties as JSON. The online generator provides you with a file ID, and you refer to the file ID in a subsequent HTTP request to retrieve your project.
This document does not explore further the use of the online generator.
Using the Generated Code
The Helidon generators go a long way in helping you write your client or server. Even so, there are important parts of your project only you can provide. This section describes your next steps after you have run the generator.
Completing the Server
Recall from earlier how the OpenAPI generator gathers operations into one or more APIs or services and generates either an abstract class or an interface your choice for each API. You need to extend each generated API class or implement each generated API interface by writing your own classes.
Any input parameters to the operations are expressed as POJO model objects or Java types as declared in the OpenAPI document. You write server code to use each of the input parameters to accomplish whatever business purpose that operation is responsible for, possibly returning a result as a POJO or Java type as indicated for that operation in the OpenAPI document.
If you choose to generate interfaces for the APIs, the generator creates routing rules for the API services it generates, but you write virtually all the logic to process incoming requests by implementing the very short methods generated in the implementation class.
The rest of this section focuses on your next steps if, on the other hand, you decide to generate abstract classes.
What you must do: implement your business logic and send the response
The generator creates an implementation class as well as the abstract class for
each API. The implementation class contains a handle method for each API
operation with a very simple method body that returns a not-yet-implemented HTTP
status in the response. The following example shows the generated method for the
addPet OpenAPI operation.
The generated handleAddPet method in the PetApiImpl class:
public class PetServiceImpl extends PetService {
@Override
protected void handleAddPet(
ServerRequest request,
ServerResponse response,
Pet pet) {
response.status(Status.NOT_IMPLEMENTED_501).send();
}
}
Customize the class to manage the pets and revise the method to save the new pet and send the correct response, as shown next.
The customized handleAddPet method in the PetApiImpl class:
If a response has any required response parameters you would pass them as
parameters to the builder method. Add optional response parameters using
other generated builder methods. The following example illustrates this for the
findPetsByTags operation and its response output parameter.
The customized findPetsByTags method in the PetApiImpl class:
Write each of the handleXxx methods appropriately so they implement the
business logic you need and send the response.
The generator creates a ResponseNNN Java record for each operation response
status code NNN that is declared in the OpenAPI document. You can return other
status values with other output parameters even if they are not declared in the
OpenAPI document, but your code must prepare the ServerResponse entirely by
itself; the generator cannot generate helper records for responses that are
absent from the document.
What you can do: override the generated behavior
Generated code takes care of the following work:
- Route each request to the method which should respond.
- Extract each incoming parameter from the request and convert it to the correct type, applying any validation declared in the OpenAPI document.
- Pass the extracted parameters to the developer-written
handleXxxmethod. - Assemble required and optional response parameters and send the response.
You can override any of the generated behavior by adding code to the generated
API implementation class you are already editing to customize the handleXxx
methods and by writing new classes which extend some of the generated classes.
Override routing
To change the way routing occurs, simply override the routing method in your
PetServiceImpl class. Make sure your custom routing handles all the paths for
which the API is responsible.
Override how to extract one or more parameters from a request
For each operation in an API the generator creates an inner class and, for each
incoming parameter for that operation, a method which extracts and validates the
parameter. Override how a parameter is extracted by following these steps, using
the AddPetOp as an example.
- Write a class which extends the inner class for the operation.
- In that subclass override the relevant method.
CustomizedAddPetOpclass: - In the implementation class for the API (
PetServiceImpl) override thecreateAddPetOpmethod so it returns an instance of your new subclassAddPetOpCustomof the operation inner classAddPetOp.
Providing your custom implementation of AddPet:public class PetServiceImpl extends PetService { @Override protected AddPetOp createAddPetOp() { return new AddPetOpCustom(); } }
Override how an operation is prepared from a request
The generated abstract class contains a method named for each operation declared
in the OpenAPI document (addPet) which accepts the Helidon request and
response as parameters. The generated code in these methods invokes the code to
extract each incoming parameter from the request, perform any declared
validation on them, and pass them to the developer-written method
(handleAddPet(request, response, pet)).
To completely change this behavior, override the addPet method in the
PetServiceImpl class to do what you need.
Using the Client Library
The generated client code represents a true library. Typically, you do not need to customize the generated client code itself. You do need to write code to invoke the code in that library.
The generated Helidon SE client includes the class ApiClient. This class
corresponds to the Helidon WebClient and represents the
connection between your code and the remote server. The generator also creates
one or more Api interfaces and corresponding implementation classes. The
examples below use the PetApi interface and the PetApiImpl class.
To invoke the remote service your code must:
- Create an instance of
ApiClientusing anApiClient.Builder. - Use that
ApiClientinstance to instantiate aPetApiobject. - Invoke the methods on the
PetApiobject to access the remote services and then retrieve the returned result value.
The following sections explain these steps.
Creating an ApiClient Instance
The Helidon SE client generator gives you as much flexibility as you need in connecting to the remote service.
Internally, the ApiClient uses a Helidon WebClient object to contact the
remote system. The ApiClient.Builder automatically prepares a Helidon
WebClientConfig.Builder object using information from the
OpenAPI document.
The next sections describe, from simplest to most complicated, the ways your
code can create an ApiClient instance, each involving increased involvement
with the WebClientConfig.Builder object.
Accepting the Automatic WebClientConfig.Builder
In the simplest case, your code can get an ApiClient instance directly.
Creating an ApiClient instance - simple case:
Your code relies fully on the automatic WebClient. In many cases, this
approach works very well, especially if the OpenAPI document correctly declares
the servers and their URIs.
Influencing the Automatic WebClientConfig.Builder
Your code can use the ApiClient.Builder to fine-tune the settings for the
internal WebClientConfig.Builder. For instance, your code can set an object
mapper to be used for Jackson processing or the JsonbConfig object to be used
for JSON-B processing, depending on which serialization library you chose when
you ran the generator.
Your code does not need to know how the object mapper setting is conveyed to the
internal WebClientConfig.Builder. The ApiClient.Builder knows how to do
that.
Creating an ApiClient instance - influencing the ApiClient.Builder:
public class ExampleClient {
private ApiClient apiClient;
void init() {
ObjectMapper myObjectMapper = new ObjectMapper();
apiClient = ApiClient.builder()
.objectMapper(myObjectMapper)
.build();
}
}
- Stores a reusable
ApiClient. - A real app would fully set up the
ObjectMapper. - Sets the object mapper for use in the
ApiClient.Builder's internalWebClientConfig.Builder.
Adjusting the Automatic WebClientConfig.Builder
In more complicated situations, your code can adjust the settings of the
WebClientConfig.Builder which the ApiClient.Builder creates.
Creating an ApiClient instance - adjusting the WebClientConfig.Builder:
public class ExampleClient {
private ApiClient apiClient;
void init() {
ApiClient.Builder apiClientAdjustedBuilder = ApiClient.builder();
apiClientAdjustedBuilder
.webClientBuilder()
.connectTimeout(Duration.ofSeconds(4));
apiClient = apiClientAdjustedBuilder.build();
}
}
- Stores a reusable
AppClient. - Creates a new
AppClientbuilder. - Access the
ApiClient.Builder’s automatic `WebClientConfig.Builderinstance. - Adjusts a setting of the
WebClientConfig.Builderdirectly. - Builds the
ApiClientwhich implicitly builds theWebClientfrom the now-adjusted internalWebClientConfig.Builder.
The automatic WebClientConfig.Builder retains information derived from the
OpenAPI document unless your code overrides those specific settings.
Providing a Custom WebClientConfig.Builder
Lastly, you can construct the WebClientConfig.Builder entirely yourself and
have the ApiClient.Builder use it instead of its own internal builder.
Creating an ApiClient instance - using a custom WebClientConfig.Builder:
public class ExampleClient {
private ApiClient apiClient;
void init() {
WebClientConfig.Builder customWebClientBuilder = WebClient.builder()
.connectTimeout(Duration.ofSeconds(3))
.baseUri("https://myservice.mycompany.com");
apiClient = ApiClient.builder()
.webClientBuilder(customWebClientBuilder)
.build();
}
}
- Stores a reusable
AppClient. - Creates a new
WebClientConfig.Builder. - Sets the connection timeout directly on the
WebClientConfig.Builder. - Sets the base URI on the
WebClienConfig.Builder. - Creates a new
ApiClient.Builder. - Sets the
WebClientConfig.Builderwhich theApiClient.Buildershould use (instead of the one it prepares internally). - Builds the
ApiClientwhich uses the newly-assignedWebClientConfig.Builderin the process.
Note that this approach entirely replaces the internal, automatically-prepared
WebClientConfig.Builder with yours; it does not merge the new builder with
the internal one. In particular, any information from the OpenAPI document the
generator used to prepare the internal WebClientConfig.Builder is lost.
Creating a PetApi Instance
The ApiClient represents the connection to the remote server but not the
individual RESTful operations. Each generated xxxApi interface exposes a
method for each operation declared in the OpenAPI document associated with that
API via its tags value. For example, the PetApi interface exposes a method
for each operation in the OpenAPI document that pertains to pets.
To invoke an operation defined on the PetApi interface, your code instantiates
a PetApi using an ApiClient object:
Preparing the PetStore Client API:
Invoking Remote Endpoints
With the petApi object, your code can invoke any of the methods on the
PetApi interface to contact the remote service.
The Helidon SE client generator creates an ApiResponse interface. Each
generated PetApi method returns an ApiResponse<returnType> where the
returnType is the return type (if any) declared in the OpenAPI document for
the corresponding operation.
The ApiResponse interface exposes two methods your code can use to work with
the response from the remote service invocation:
T result()
Provides access to the value returned by the remote service in the response. This method lets your code fetch the return value directly.HTTPClientResponse webClientResponse()
Provides access to the HelidonHTTPClientResponseobject. Your code can find out the HTTP return status, read headers in the response, and process the content (if any) in the response however it needs to.
In the Helidon WebClient model, the first part of the response message can arrive (the status and headers are available) before the entity in the body of the response is readable. So there are two events associated with an incoming HTTP response:
- when the response excluding the entity content has arrived, and
- when your code can begin consuming the entity content.
You can adopt different styles of retrieving the results, depending on the specific needs of the code you are writing.
Access only the result
Access with only result access:
Access with status checking
The Helidon WebClient programming model includes a HTTPClientResponse
interface which exposes all aspects of the HTTP response returned from the
remote service.
The next example shows how your code can use the HTTPClientResponse.
Access with status checking:
This code also blocks the current thread, first to wait for the initial response and then to wait for the result content.