Enterprise Java

OpenAPI Generator Custom Templates

Photo of Mary ZhengMary ZhengMay 6th, 2024Last Updated: May 6th, 2024
0 2,950 12 minutes read

1. Introduction

Open API is a specification for designing and documenting RESTful APIs.OpenAPI generator is a tool used in API-first development as it can generate client and server source code from OpenAPI 2.0/3.x documents. It supports multiple languages and frameworks. Although most of the time the generated code is ready to be used without modification, there are scenarios in which we need to customize it. In this tutorial, I will demonstrate how to use spring boot openapi generator custom templates in the following steps:

  • Create a maven project and configure “openapi-generator-maven-plugin“.
  • Create an OpenAPI specification –products.yaml.
  • Execute the mavengenerate-source command to generate source code from theproducts.yaml file.
  • Create an implementation class for the generated interface.
  • Create Junit tests.
  • Update theproducts.yaml file with the “x-spring-cacheable” vendor-specific extension with the default setting.
  • Update theproducts.yaml file with the “x-spring-cacheable” vendor-specific extension and set the cache name.
  • Re-generate the source code with the built-in template and test with Junit tests.
  • Re-generate the source code with a custom template and test with Junit tests.

2. Setup Maven Project for OpenAPI Generator

OpenAPI generator supportsa wide variety of generators. In this example, I will add “openapi-generator-maven-plugin” in thepom.xml file and generate code based on theproducts.yaml file via the “spring” generator.

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><artifactId>spring-boot-openapi</artifactId><name>spring-boot-openapi</name><packaging>jar</packaging><description>OpenAPI Generator module</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.5</version><relativePath /></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>jakarta.validation</groupId><artifactId>jakarta.validation-api</artifactId></dependency><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency><dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-annotations</artifactId><version>2.2.20</version></dependency></dependencies><build><plugins><plugin><groupId>org.openapitools</groupId><artifactId>openapi-generator-maven-plugin</artifactId><version>7.5.0</version><executions><execution><goals><goal>generate</goal></goals><configuration><inputSpec>${project.basedir}/src/main/resources/api/products.yaml</inputSpec><generatorName>spring</generatorName><supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate><!--<templateResourcePath>${project.basedir}/src/main/resources/templates/JavaSpring</templateResourcePath>--><globalProperties><debugOpenAPI>true</debugOpenAPI></globalProperties><configOptions><delegatePattern>true</delegatePattern><apiPackage>com.zheng.demo.openapi.products.api</apiPackage><modelPackage>com.zheng.demo.openapi.products.api.model</modelPackage><documentationProvider>source</documentationProvider><dateLibrary>java8</dateLibrary><openApiNullable>false</openApiNullable><useJakartaEe>true</useJakartaEe><useSpringBoot3>true</useSpringBoot3></configOptions></configuration></execution></executions></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>
  • line 54: add theopenapi-generator-maven-plugin plugin.
  • line 63: config theinputSpec with the products.yaml.
  • line 64, use the “spring” generator.
  • line 73: setdelegatePattern totrue.
  • line 74: nameapiPackage tocom.zheng.demo.openapi.products.api.
  • line 75: namemodelPackage tocom.zheng.demo.openapi.products.api.model.
  • line 80: setuseJakartaEe totrue to use the Jakarta validation.
  • line 81: setuseSpringBoot3 totrue.

Launchswagger editor at any browser and create a simple RestFul API specification to create and get a product. Save the specification in theproducts.yaml file.

Here is the screenshot of products API.

Figure 1. Product APIs

Here is the product API’s YAML specification.

products.yaml

openapi: 3.0.0info:  title: Product API  version: 1.0.0servers:  - description: Test server    url: http://localhost:8080paths:  /products/{id}:    get:      tags:        - products      summary: Get product detail for a given product id      operationId: getProduct      security:        - ApiKey:            - Product.Read      parameters:        - name: id          in: path          required: true          description: Product's identifier          schema:            type: number                  responses:        '200':            description: OK            content:              application/json:                schema:                  $ref: '#/components/schemas/ProductDO'  /products:      post:      tags:        - products      summary: Create a product      operationId: createProduct      requestBody:        description: Create a new prodcut in the store        content:          application/json:            schema:              $ref: '#/components/schemas/ProductDO'        required: true      security:        - ApiKey:            - Product.Create      responses:        '200':            description: OK            content:              application/json:                schema:                  $ref: '#/components/schemas/ProductDO'components:  securitySchemes:    ApiKey:      type: apiKey      in: header      name: X-API-KEY  schemas:    ProductDO:      description: Product Information      type: object      properties:        id:          type: number          description: product id        name:          type: string          description: product name        price:          type: number          description: product price value

Copy theproducts.yaml file and create two versions so there are 3 files underresources\api folder:

api folder

C:\MaryTools\workspace\JCG\spring-boot-openapi\src\main\resources\api>dir Volume in drive C is OS Volume Serial Number is 92BA-6AB7 Directory of C:\MaryTools\workspace\JCG\spring-boot-openapi\src\main\resources\api05/04/2024  07:56 PM              .05/03/2024  09:13 PM              ..05/04/2024  07:55 PM             1,734 products.yaml05/04/2024  07:40 PM             1,765 products1.yaml05/04/2024  08:59 AM             1,786 products2.yaml               3 File(s)          5,285 bytes               2 Dir(s)  109,910,233,088 bytes freeC:\MaryTools\workspace\JCG\spring-boot-openapi\src\main\resources\api>
  • Theproducts.yaml has no vendor-specific extension, therefore can be generated without any customization.
  • Theproducts1.yaml file has a vendor-spec extension and sets thex-spring-cacheable astrue.
  • Theproducts2.yaml file sets thex-spring-cacheable with the defined cache name.

The difference betweenproducts1.yaml andproducts2.yaml is showing in the following screenshot:

Figure 2. Products.yaml x-spring-cacheable Difference

3. API Implementation

In this step, I will generate a spring boot server stub with the “spring” generator via its built-in template and create the implementation class and test the generated code with Junit tests.

3.1 Create Spring Boot Application

Create a spring boot application and annotate with both@SpringBootApplication and@EnableCaching.

ProductApplication.java

package com.zheng.demo.openapi.products;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication@EnableCachingpublic class ProductsApplication {    public static void main(String[] args) {        SpringApplication.run(ProductsApplication.class, args);    }}

3.2 Create Implementation Classes

In this step, I will create an implementation class for the generated interface with mocked data.

ProductsApiImpl.java

package com.zheng.demo.openapi.products.service;import java.math.BigDecimal;import java.util.Random;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Component;import com.zheng.demo.openapi.products.api.ProductsApiDelegate;import com.zheng.demo.openapi.products.api.model.ProductDO;@Componentpublic class ProductsApiImpl implements ProductsApiDelegate {Logger logger = LoggerFactory.getLogger(ProductsApiImpl.class);private final Random rnd = new Random();@Overridepublic ResponseEntity<ProductDO> getProduct(BigDecimal id) {logger.info("getProduct called");ProductDO prod = new ProductDO();prod.setId(id);prod.setName("Product_" + id);prod.setPrice(BigDecimal.valueOf(100.0 + rnd.nextDouble() * 100.0));return ResponseEntity.ok(prod);}@Overridepublic ResponseEntity<ProductDO> createProduct(ProductDO product) {logger.info("createProduct called");product.setId(BigDecimal.valueOf(rnd.nextDouble() * 10));return ResponseEntity.ok(product);}}
  • line 21, log a statement whengetProduct service is called. It is used to verify if the cache is used or not.
  • line 32, log a statement when thecreateProduct service is called.

3.3 Application.yaml

Set the logging application properties and thebase-path properties. We use the log statements to verify if the cache data is used or not.

application.yaml

logging:  level:    root: INFO    org.springframework: INFOopenapi:  product:    base-path: v1

4. Generated Source Code

Execute themvn generate-source command and review the generated java source files.

4.1 ApiUtil Class

TheApiUtil class has only one static methodsetExampleResponse.

ApiUtil.java

package com.zheng.demo.openapi.products.api;import org.springframework.web.context.request.NativeWebRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;public class ApiUtil {    public static void setExampleResponse(NativeWebRequest req, String contentType, String example) {        try {            HttpServletResponse res = req.getNativeResponse(HttpServletResponse.class);            res.setCharacterEncoding("UTF-8");            res.addHeader("Content-Type", contentType);            res.getWriter().print(example);        } catch (IOException e) {            throw new RuntimeException(e);        }    }}

Note: TheApiUtil.java file has the same content when generated from both built-in template and custom template.

4.2 ProductsApi Interface

TheProductsApi interface includes 3 defaults methods:

  • getDelegate is from thedelegatePattern setting.
  • createProduct andgetProduct are defined in theProducts.yaml file under theoperation section.

ProductsApi.java

/** * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (7.5.0). * https://openapi-generator.tech * Do not edit the class manually. */package com.zheng.demo.openapi.products.api;import java.math.BigDecimal;import com.zheng.demo.openapi.products.api.model.ProductDO;import org.springframework.http.ResponseEntity;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import jakarta.validation.Valid;import jakarta.validation.constraints.*;import java.util.List;import java.util.Map;import jakarta.annotation.Generated;@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-05-04T23:36:23.308233300-05:00[America/Chicago]", comments = "Generator version: 7.5.0")@Validatedpublic interface ProductsApi {    default ProductsApiDelegate getDelegate() {        return new ProductsApiDelegate() {};    }    /**     * POST /products : Create a product     *     * @param productDO Create a new prodcut in the store (required)     * @return OK (status code 200)     */    @RequestMapping(        method = RequestMethod.POST,        value = "/products",        produces = { "application/json" },        consumes = { "application/json" }    )        default ResponseEntity<ProductDO> createProduct(         @Valid @RequestBody ProductDO productDO    ) {        return getDelegate().createProduct(productDO);    }    /**     * GET /products/{id} : Get product detail for a given product id     *     * @param id Product's identifier (required)     * @return OK (status code 200)     */    @RequestMapping(        method = RequestMethod.GET,        value = "/products/{id}",        produces = { "application/json" }    )        default ResponseEntity<ProductDO> getProduct(         @PathVariable("id") BigDecimal id    ) {        return getDelegate().getProduct(id);    }}

Note: TheProductsApi file has the same content when generated from both built-in template and custom template.

4.3 ProductApiDelegate Interface

TheProductApiDelegate interface has the same methods as theProductApi interface.

ProductsApiDelegate.java

package com.zheng.demo.openapi.products.api;import java.math.BigDecimal;import com.zheng.demo.openapi.products.api.model.ProductDO;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.multipart.MultipartFile;import jakarta.validation.constraints.*;import jakarta.validation.Valid;import java.util.List;import java.util.Map;import java.util.Optional;import jakarta.annotation.Generated;/** * A delegate to be called by the {@link ProductsApiController}}. * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class. */@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-05-04T23:36:23.308233300-05:00[America/Chicago]", comments = "Generator version: 7.5.0")public interface ProductsApiDelegate {    default Optional<NativeWebRequest> getRequest() {        return Optional.empty();    }    /**     * POST /products : Create a product     *     * @param productDO Create a new prodcut in the store (required)     * @return OK (status code 200)     * @see ProductsApi#createProduct     */    default ResponseEntity<ProductDO> createProduct(ProductDO productDO) {        getRequest().ifPresent(request -> {            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {                    String exampleString = "{ \"price\" : 6.027456183070403, \"name\" : \"name\", \"id\" : 0.8008281904610115 }";                    ApiUtil.setExampleResponse(request, "application/json", exampleString);                    break;                }            }        });        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);    }    /**     * GET /products/{id} : Get product detail for a given product id     *     * @param id Product's identifier (required)     * @return OK (status code 200)     * @see ProductsApi#getProduct     */    default ResponseEntity<ProductDO> getProduct(BigDecimal id) {        getRequest().ifPresent(request -> {            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {                    String exampleString = "{ \"price\" : 6.027456183070403, \"name\" : \"name\", \"id\" : 0.8008281904610115 }";                    ApiUtil.setExampleResponse(request, "application/json", exampleString);                    break;                }            }        });        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);    }}

Note: The OpenAPI built-in template does not know how to transform thex-spring-cacheable extension, so developers must add the@org.springframework.cache.annotation.Cacheable("default") annotation to the generated code in order to pass the integration test.

4.4 ProductApiController Class

The generatedProductsApiController class has a “openapi.product.base-path” property. If the property is not defined, then it falls back to an empty string"". Please refer tostep 3.3 as thebase-path value is set to “v1“.

ProductsApiController.java

package com.zheng.demo.openapi.products.api;import java.math.BigDecimal;import com.zheng.demo.openapi.products.api.model.ProductDO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.CookieValue;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RequestPart;import org.springframework.web.multipart.MultipartFile;import jakarta.validation.constraints.*;import jakarta.validation.Valid;import java.util.List;import java.util.Map;import java.util.Optional;import javax.annotation.Generated;@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-05-04T07:47:53.432603-05:00[America/Chicago]")@Controller@RequestMapping("${openapi.product.base-path:}")public class ProductsApiController implements ProductsApi {    private final ProductsApiDelegate delegate;    public ProductsApiController(@Autowired(required = false) ProductsApiDelegate delegate) {        this.delegate = Optional.ofNullable(delegate).orElse(new ProductsApiDelegate() {});    }    @Override    public ProductsApiDelegate getDelegate() {        return delegate;    }}

Note: TheProductsApiController file has the same content when generated from both built-in template and custom template.

4.5 ProductDO Class

The generatedProductDO class defines theProduct data model.

ProductDO.java

package com.zheng.demo.openapi.products.api.model;import java.net.URI;import java.util.Objects;import com.fasterxml.jackson.annotation.JsonProperty;import com.fasterxml.jackson.annotation.JsonCreator;import java.math.BigDecimal;import java.time.OffsetDateTime;import jakarta.validation.Valid;import jakarta.validation.constraints.*;import java.util.*;import jakarta.annotation.Generated;/** * Product Information */@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-05-04T23:36:23.308233300-05:00[America/Chicago]", comments = "Generator version: 7.5.0")public class ProductDO {  private BigDecimal id;  private String name;  private BigDecimal price;  public ProductDO id(BigDecimal id) {    this.id = id;    return this;  }  /**   * product id   * @return id  */  @Valid   @JsonProperty("id")  public BigDecimal getId() {    return id;  }  public void setId(BigDecimal id) {    this.id = id;  }  public ProductDO name(String name) {    this.name = name;    return this;  }  /**   * product name   * @return name  */    @JsonProperty("name")  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  public ProductDO price(BigDecimal price) {    this.price = price;    return this;  }  /**   * product price value   * @return price  */  @Valid   @JsonProperty("price")  public BigDecimal getPrice() {    return price;  }  public void setPrice(BigDecimal price) {    this.price = price;  }  @Override  public boolean equals(Object o) {    if (this == o) {      return true;    }    if (o == null || getClass() != o.getClass()) {      return false;    }    ProductDO productDO = (ProductDO) o;    return Objects.equals(this.id, productDO.id) &&        Objects.equals(this.name, productDO.name) &&        Objects.equals(this.price, productDO.price);  }  @Override  public int hashCode() {    return Objects.hash(id, name, price);  }  @Override  public String toString() {    StringBuilder sb = new StringBuilder();    sb.append("class ProductDO {\n");    sb.append("    id: ").append(toIndentedString(id)).append("\n");    sb.append("    name: ").append(toIndentedString(name)).append("\n");    sb.append("    price: ").append(toIndentedString(price)).append("\n");    sb.append("}");    return sb.toString();  }  /**   * Convert the given object to string with each line indented by 4 spaces   * (except the first line).   */  private String toIndentedString(Object o) {    if (o == null) {      return "null";    }    return o.toString().replace("\n", "\n    ");  }}

5. Junit Test Classes

5.1 Unit Test

ProductsApiImplUnitTest has 2 tests which test bothgetProduct andcreateProduct services.

ProductsApiImplUnitTest.java

package com.zheng.demo.openapi.products.service;import static org.assertj.core.api.Assertions.assertThat;import java.math.BigDecimal;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.ResponseEntity;import com.zheng.demo.openapi.products.api.ProductsApi;import com.zheng.demo.openapi.products.api.model.ProductDO;@SpringBootTestclass ProductsApiImplUnitTest {@Autowiredprivate ProductsApi api;@Testvoid whenGetProduct_then_success() {ResponseEntity<ProductDO> response = api.getProduct(new BigDecimal(1));assertThat(response).isNotNull();assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();}@Testvoid whenCreateProduct_then_success() {ProductDO product = new ProductDO();product.setName("Test");product.setPrice(new BigDecimal(100));ResponseEntity<ProductDO> response = api.createProduct(product);assertThat(response).isNotNull();assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();}}

Execute the junit tests and both passed as expected.

5.2 Integration Test

ProductsApplicationIntegrationTest has 3 tests which test bothgetProduct andcreateProduct at the test server. ThegetProduct should use cache for the same product id.

ProductsApplicationIntegrationTest.java

package com.zheng.demo.openapi.products;import static org.assertj.core.api.Assertions.assertThat;import java.util.stream.Collectors;import java.util.stream.IntStream;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import org.springframework.boot.test.web.server.LocalServerPort;import org.springframework.http.HttpEntity;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import com.zheng.demo.openapi.products.api.model.ProductDO;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)class ProductsApplicationIntegrationTest {@LocalServerPortprivate int port;@Autowiredprivate TestRestTemplate restTemplate;@Testvoid whenGetProduct_thenSuccess() {ResponseEntity<ProductDO> response = restTemplate.getForEntity("http://localhost:" + port + "/v1/products/1",ProductDO.class);assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);}@Testvoid whenGetProductMultipleTimes_thenResponseCached() {// Call server a few times and collect responsesvar quotes = IntStream.range(1, 10).boxed().map((i) -> restTemplate.getForEntity("http://localhost:" + port + "/v1/products/1", ProductDO.class)).map(HttpEntity::getBody).collect(Collectors.groupingBy((q -> q.hashCode()), Collectors.counting()));assertThat(quotes.size()).isEqualTo(1);}@Testvoid whenCreateProduct_thenSuccess() {ProductDO product = new ProductDO();product.setName("TEST");ResponseEntity<ProductDO> response = restTemplate.postForEntity("http://localhost:" + port + "/v1/products",product, ProductDO.class);assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);}}

ThewhenGetProductMultipleTimes_thenResponseCached test failed as thegetProduct service was called multiple times.

2024-05-04T23:28:54.048-05:00  INFO 36452 --- [o-auto-1-exec-2] c.z.d.o.p.service.ProductsApiImpl        : createProduct called2024-05-04T23:28:54.150-05:00  INFO 36452 --- [o-auto-1-exec-5] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.164-05:00  INFO 36452 --- [o-auto-1-exec-4] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.168-05:00  INFO 36452 --- [o-auto-1-exec-1] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.173-05:00  INFO 36452 --- [o-auto-1-exec-3] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.178-05:00  INFO 36452 --- [o-auto-1-exec-6] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.181-05:00  INFO 36452 --- [o-auto-1-exec-7] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.185-05:00  INFO 36452 --- [o-auto-1-exec-8] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.189-05:00  INFO 36452 --- [o-auto-1-exec-9] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.193-05:00  INFO 36452 --- [-auto-1-exec-10] c.z.d.o.p.service.ProductsApiImpl        : getProduct called2024-05-04T23:28:54.196-05:00  INFO 36452 --- [o-auto-1-exec-2] c.z.d.o.p.service.ProductsApiImpl        : getProduct called

Note: thex-spring-cacheable is not supported by the default template, therefore thegetProduct service is called 10 times instead of one time.

6. Spring Boot OpenAPI Generator Custom Templates

As seen instep 5, the generated spring boot server stub source code missed the cache annotation. In this step, I will update the built-inapiDelegate.mustache template to add the “x-spring-cacheable” vendor extension and use it when generating the code from theproducts1.yaml.

/** Generated code: do not modify !* Custom template with support for x-spring-cacheable extension*/package {{package}};{{#imports}}import {{import}};{{/imports}}import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;{{#useResponseEntity}}    import org.springframework.http.ResponseEntity;{{/useResponseEntity}}import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.multipart.MultipartFile;{{#reactive}}    import org.springframework.web.server.ServerWebExchange;    import reactor.core.publisher.Flux;    import reactor.core.publisher.Mono;    import org.springframework.http.codec.multipart.Part;{{/reactive}}{{#useBeanValidation}}    import {{javaxPackage}}.validation.constraints.*;    import {{javaxPackage}}.validation.Valid;{{/useBeanValidation}}import java.util.List;import java.util.Map;import java.util.Optional;{{#async}}    import java.util.concurrent.CompletableFuture;{{/async}}import {{javaxPackage}}.annotation.Generated;{{#operations}}    /**    * A delegate to be called by the {@link {{classname}}Controller}}.    * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class.    */    {{>generatedAnnotation}}    public interface {{classname}}Delegate {    {{#jdk8-default-interface}}        default Optional<NativeWebRequest> getRequest() {            return Optional.empty();            }    {{/jdk8-default-interface}}    {{#operation}}            /**            * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}}        {{#notes}}                * {{.}}        {{/notes}}            *        {{#allParams}}                * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}        {{/allParams}}            * @return {{#responses}}{{message}} (status code {{code}}){{^-last}}                *         or {{/-last}}{{/responses}}        {{#isDeprecated}}                * @deprecated        {{/isDeprecated}}        {{#externalDocs}}                * {{description}}                * @see <a href="{{url}}">{{summary}} Documentation</a>        {{/externalDocs}}            * @see {{classname}}#{{operationId}}            */        {{#isDeprecated}}                @Deprecated        {{/isDeprecated}}        {{#vendorExtensions.x-spring-cacheable}}        @org.springframework.cache.annotation.Cacheable({{#name}}"{{.}}"{{/name}}{{^name}}"default"{{/name}})        {{/vendorExtensions.x-spring-cacheable}}        {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#isArray}}List<{{/isArray}}{{#reactive}}Flux<Part>{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}}{{/isFile}} {{paramName}}{{^-last}},        {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}},        {{/hasParams}}ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} {        {{>methodBody}}            }{{/jdk8-default-interface}}    {{/operation}}        }{{/operations}}
  • line 73-75: support thex-spring-cacheable vendor-specific extension.

6.1 Use Products1.yaml

Update thepom.xml to specifytemplateResourcePath with the custom template and useproducts1.xml. As you seen atstep 2, theproducts1.yaml includesx-spring-cacheable: true

Updated pom.xml at the configuration section

<configuration><inputSpec>${project.basedir}/src/main/resources/api/products1.yaml</inputSpec><generatorName>spring</generatorName><supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate><templateResourcePath>${project.basedir}/src/main/resources/templates/JavaSpring</templateResourcePath><globalProperties><debugOpenAPI>true</debugOpenAPI></globalProperties><configOptions><delegatePattern>true</delegatePattern><apiPackage>com.zheng.demo.openapi.products.api</apiPackage><modelPackage>com.zheng.demo.openapi.products.api.model</modelPackage><documentationProvider>source</documentationProvider><dateLibrary>java8</dateLibrary><openApiNullable>false</openApiNullable><useJakartaEe>true</useJakartaEe><useSpringBoot3>true</useSpringBoot3></configOptions>

Note: line 7 specifies thetemplateResourcePath file location.

6.2 Re-generate the Source

Run themvn generate-source command and it will generate five files as thestep 4. All files have the same content exceptProductsApiDelegate now added@org.springframework.cache.annotation.Cacheable("default") to the getProduct method.

ProductsApiDelegate.java‘sgetProduct method

  @org.springframework.cache.annotation.Cacheable("default")        default ResponseEntity<ProductDO> getProduct(BigDecimal id) {        getRequest().ifPresent(request -> {            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {                    String exampleString = "{ \"price\" : 6.027456183070403, \"name\" : \"name\", \"id\" : 0.8008281904610115 }";                    ApiUtil.setExampleResponse(request, "application/json", exampleString);                    break;                }            }        });        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);            }

Note: line 1, the@org.springframework.cache.annotation.Cacheable("default") is added.

7. Using the Modified Template

In this step, I will update thepom.xml to specify the template file and then re-generate the source code. this time, it will include the@cacheable annotation.

7.1 Use Products2.yaml

Update thepom.xml at theinputSpec to useproducts2.yaml. Refer tostep 2,products2.yaml specifies thex-spring-cacheable: name: get-product

7.2 Re-generate the Source

  @org.springframework.cache.annotation.Cacheable("get-product")        default ResponseEntity<ProductDO> getProduct(BigDecimal id) {        getRequest().ifPresent(request -> {            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {                    String exampleString = "{ \"price\" : 6.027456183070403, \"name\" : \"name\", \"id\" : 0.8008281904610115 }";                    ApiUtil.setExampleResponse(request, "application/json", exampleString);                    break;                }            }        });        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);            }

Note: line 1, the@org.springframework.cache.annotation.Cacheable("get-product") is added.

Run tests and now all passed as the cache is used.

8. Conclusion

In this tutorial, I demonstrated how to configure the OpenAPI Generator tool in a spring boot maven project and generate source code from an open API specification with a custom template that supports a simple vendor extension. The spring boot openapi generator custom templates are used in the following steps.

  • Execute the mavengenerate-source command to generate source code from theproducts.yaml file.
  • Update theproducts.yaml file with the “x-spring-cacheable” vendor-specific extension with the default setting and generate from a custom template.
  • Update theproducts.yaml file with the “x-spring-cacheable” vendor-specific extension and set the cache name and generate from a custom template.

9. Download

This was an example of Spring boot maven project which generates source code from a custom template.

Download
You can download the full source code of this example here:OpenAPI Generator Custom Templates
Do you want to know how to develop your skillset to become aJava Rockstar?
Subscribe to our newsletter to start Rockingright now!
To get you started we give you our best selling eBooks forFREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to theTerms andPrivacy Policy

Thank you!

We will contact you soon.

Photo of Mary ZhengMary ZhengMay 6th, 2024Last Updated: May 6th, 2024
0 2,950 12 minutes read
Photo of Mary Zheng

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest
I agree to theTerms andPrivacy Policy
The comment form collects your name, email and content to allow us keep track of the comments placed on the website. Please read and accept our website Terms and Privacy Policy to post a comment.

I agree to theTerms andPrivacy Policy
The comment form collects your name, email and content to allow us keep track of the comments placed on the website. Please read and accept our website Terms and Privacy Policy to post a comment.