{"id":122156,"date":"2024-05-06T13:14:37","date_gmt":"2024-05-06T10:14:37","guid":{"rendered":"https:\/\/www.javacodegeeks.com\/?p=122156"},"modified":"2024-05-06T13:19:10","modified_gmt":"2024-05-06T10:19:10","slug":"openapi-generator-custom-templates","status":"publish","type":"post","link":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html","title":{"rendered":"OpenAPI Generator Custom Templates"},"content":{"rendered":"<h2 class=\"wp-block-heading\">1. Introduction<\/h2>\n<p><a href=\"https:\/\/swagger.io\/specification\/\" target=\"_blank\" rel=\"noreferrer noopener\">Open API<\/a> is a specification for designing and documenting RESTful APIs. <a href=\"https:\/\/openapi-generator.tech\/\" target=\"_blank\" rel=\"noreferrer noopener\">OpenAPI generator<\/a> 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:<\/p>\n<ul class=\"wp-block-list\">\n<li>Create a maven project and configure &#8220;<code>openapi-generator-maven-plugin<\/code>&#8220;.<\/li>\n<li>Create an OpenAPI specification &#8211; <code>products.yaml<\/code>.<\/li>\n<li>Execute the maven <code>generate-source<\/code> command to generate source code from the <code>products.yaml<\/code> file.<\/li>\n<li>Create an implementation class for the generated interface.<\/li>\n<li>Create Junit tests.<\/li>\n<li>Update the <code>products.yaml<\/code> file with the &#8220;<code>x-spring-cacheable<\/code>&#8221; vendor-specific extension with the default setting.<\/li>\n<li>Update the <code>products.yaml<\/code> file with the &#8220;<code>x-spring-cacheable<\/code>&#8221; vendor-specific extension and set the cache name.<\/li>\n<li>Re-generate the source code with the built-in template and test with Junit tests.<\/li>\n<li>Re-generate the source code with a custom template and test with Junit tests.<\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\"><a name=\"step2\"><\/a>2. Setup Maven Project for OpenAPI Generator<\/h2>\n<p>OpenAPI generator supports <a href=\"https:\/\/openapi-generator.tech\/docs\/generators\/\" target=\"_blank\" rel=\"noreferrer noopener\">a wide variety of generators<\/a>. In this example, I will add &#8220;<code>openapi-generator-maven-plugin<\/code>&#8221; in the <code>pom.xml<\/code> file and generate code based on the <code>products.yaml<\/code> file via the &#8220;<code>spring<\/code>&#8221; generator.<\/p>\n<p><span style=\"text-decoration: underline\"><em>pom.xml<\/em><\/span><\/p>\n<pre class=\"brush:xml; highlight:[54,63,64,73,74,75,80,81]\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;project xmlns=\"http:\/\/maven.apache.org\/POM\/4.0.0\"\n\txmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"\n\txsi:schemaLocation=\"http:\/\/maven.apache.org\/POM\/4.0.0 http:\/\/maven.apache.org\/xsd\/maven-4.0.0.xsd\"&gt;\n\t&lt;modelVersion&gt;4.0.0&lt;\/modelVersion&gt;\n\t&lt;artifactId&gt;spring-boot-openapi&lt;\/artifactId&gt;\n\t&lt;name&gt;spring-boot-openapi&lt;\/name&gt;\n\t&lt;packaging&gt;jar&lt;\/packaging&gt;\n\t&lt;description&gt;OpenAPI Generator module&lt;\/description&gt;\n\n\t&lt;parent&gt;\n\t\t&lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n\t\t&lt;artifactId&gt;spring-boot-starter-parent&lt;\/artifactId&gt;\n\t\t&lt;version&gt;3.2.5&lt;\/version&gt;\n\t\t&lt;relativePath \/&gt;\n\t&lt;\/parent&gt;\n\n\t&lt;dependencies&gt;\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n\t\t&lt;\/dependency&gt;\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;spring-boot-starter-test&lt;\/artifactId&gt;\n\t\t\t&lt;scope&gt;test&lt;\/scope&gt;\n\t\t&lt;\/dependency&gt;\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;spring-boot-starter-validation&lt;\/artifactId&gt;\n\t\t&lt;\/dependency&gt;\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;jakarta.validation&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;jakarta.validation-api&lt;\/artifactId&gt;\n\t\t&lt;\/dependency&gt;\n\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;javax.annotation&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;javax.annotation-api&lt;\/artifactId&gt;\n\t\t\t&lt;version&gt;1.3.2&lt;\/version&gt;\n\t\t&lt;\/dependency&gt;\n\t\t&lt;dependency&gt;\n\t\t\t&lt;groupId&gt;io.swagger.core.v3&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;swagger-annotations&lt;\/artifactId&gt;\n\t\t\t&lt;version&gt;2.2.20&lt;\/version&gt;\n\t\t&lt;\/dependency&gt;\n\n\t&lt;\/dependencies&gt;\n\n\t&lt;build&gt;\n\t\t&lt;plugins&gt;\n\t\t\t&lt;plugin&gt;\n\t\t\t\t&lt;groupId&gt;org.openapitools&lt;\/groupId&gt;\n\t\t\t\t&lt;artifactId&gt;openapi-generator-maven-plugin&lt;\/artifactId&gt;\n\t\t\t\t&lt;version&gt;7.5.0&lt;\/version&gt;\n\t\t\t\t&lt;executions&gt;\n\t\t\t\t\t&lt;execution&gt;\n\t\t\t\t\t\t&lt;goals&gt;\n\t\t\t\t\t\t\t&lt;goal&gt;generate&lt;\/goal&gt;\n\t\t\t\t\t\t&lt;\/goals&gt;\n\t\t\t\t\t\t&lt;configuration&gt;\n\t\t\t\t\t\t\t&lt;inputSpec&gt;\n\t\t\t\t\t\t\t\t${project.basedir}\/src\/main\/resources\/api\/products.yaml&lt;\/inputSpec&gt;\n\t\t\t\t\t\t\t&lt;generatorName&gt;spring&lt;\/generatorName&gt;\n\t\t\t\t\t\t\t&lt;supportingFilesToGenerate&gt;ApiUtil.java&lt;\/supportingFilesToGenerate&gt;\n\t\t\t\t\t\t&lt;!--\t&lt;templateResourcePath&gt;\n\t\t\t\t\t\t\t\t${project.basedir}\/src\/main\/resources\/templates\/JavaSpring\n\t\t\t\t\t\t\t&lt;\/templateResourcePath&gt;--&gt;\n\t\t\t\t\t\t\t&lt;globalProperties&gt;\n\t\t\t\t\t\t\t\t&lt;debugOpenAPI&gt;true&lt;\/debugOpenAPI&gt;\n\t\t\t\t\t\t\t&lt;\/globalProperties&gt;\n\t\t\t\t\t\t\t&lt;configOptions&gt;\n\t\t\t\t\t\t\t\t&lt;delegatePattern&gt;true&lt;\/delegatePattern&gt;\n\t\t\t\t\t\t\t\t&lt;apiPackage&gt;com.zheng.demo.openapi.products.api&lt;\/apiPackage&gt;\n\t\t\t\t\t\t\t\t&lt;modelPackage&gt;\n\t\t\t\t\t\t\t\t\tcom.zheng.demo.openapi.products.api.model&lt;\/modelPackage&gt;\n\t\t\t\t\t\t\t\t&lt;documentationProvider&gt;source&lt;\/documentationProvider&gt;\n\t\t\t\t\t\t\t\t&lt;dateLibrary&gt;java8&lt;\/dateLibrary&gt;\n\t\t\t\t\t\t\t\t&lt;openApiNullable&gt;false&lt;\/openApiNullable&gt;\n\t\t\t\t\t\t\t\t&lt;useJakartaEe&gt;true&lt;\/useJakartaEe&gt;\n\t\t\t\t\t\t\t\t&lt;useSpringBoot3&gt;true&lt;\/useSpringBoot3&gt;\n\t\t\t\t\t\t\t&lt;\/configOptions&gt;\n\t\t\t\t\t\t&lt;\/configuration&gt;\n\t\t\t\t\t&lt;\/execution&gt;\n\t\t\t\t&lt;\/executions&gt;\n\t\t\t&lt;\/plugin&gt;\n\t\t\t&lt;plugin&gt;\n\t\t\t\t&lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n\t\t\t\t&lt;artifactId&gt;spring-boot-maven-plugin&lt;\/artifactId&gt;\n\t\t\t&lt;\/plugin&gt;\n\t\t&lt;\/plugins&gt;\n\t&lt;\/build&gt;\n\n\t&lt;properties&gt;\n\t\t&lt;maven.compiler.source&gt;17&lt;\/maven.compiler.source&gt;\n\t\t&lt;maven.compiler.target&gt;17&lt;\/maven.compiler.target&gt;\n\t&lt;\/properties&gt;\n\n&lt;\/project&gt;<\/pre>\n<ul class=\"wp-block-list\">\n<li>line 54: add the <code>openapi-generator-maven-plugin<\/code> plugin.<\/li>\n<li>line 63: config the <code>inputSpec<\/code> with the products.yaml.<\/li>\n<li>line 64, use the &#8220;<code>spring<\/code>&#8221; generator.<\/li>\n<li>line 73: set <code>delegatePattern<\/code> to <code>true<\/code>.<\/li>\n<li>line 74: name <code>apiPackage<\/code> to <code>com.zheng.demo.openapi.products.api<\/code>.<\/li>\n<li>line 75: name <code>modelPackage<\/code> to <code>com.zheng.demo.openapi.products.api.model<\/code>.<\/li>\n<li>line 80: set <code>useJakartaEe<\/code> to <code>true<\/code> to use the Jakarta validation.<\/li>\n<li>line 81: set <code>useSpringBoot3<\/code> to <code>true<\/code>.<\/li>\n<\/ul>\n<p>Launch <a href=\"https:\/\/editor.swagger.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">swagger editor<\/a> at any browser and create a simple RestFul API specification to create and get a product. Save the specification in the <code>products.yaml<\/code> file. <\/p>\n<p>Here is the screenshot of products API.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/swaggeruiAPI.jpg\"><img decoding=\"async\" width=\"595\" height=\"546\" src=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/swaggeruiAPI.jpg\" alt=\"\" class=\"wp-image-122287\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/swaggeruiAPI.jpg 595w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/swaggeruiAPI-300x275.jpg 300w\" sizes=\"(max-width: 595px) 100vw, 595px\" \/><\/a><figcaption class=\"wp-element-caption\">Figure 1. Product APIs<\/figcaption><\/figure>\n<\/div>\n<p>Here is the product API&#8217;s YAML specification.<\/p>\n<p><span style=\"text-decoration: underline\"><em>products.yaml<\/em><\/span><\/p>\n<pre class=\"brush:plain; wrap-lines:false\">openapi: 3.0.0\ninfo:\n  title: Product API\n  version: 1.0.0\nservers:\n  - description: Test server\n    url: http:\/\/localhost:8080\npaths:\n  \/products\/{id}:\n    get:\n      tags:\n        - products\n      summary: Get product detail for a given product id\n      operationId: getProduct\n      security:\n        - ApiKey:\n            - Product.Read\n      parameters:\n        - name: id\n          in: path\n          required: true\n          description: Product's identifier\n          schema:\n            type: number\n            \n      responses:\n        '200':\n            description: OK\n            content:\n              application\/json:\n                schema:\n                  $ref: '#\/components\/schemas\/ProductDO'\n  \/products:  \n    post:\n      tags:\n        - products\n      summary: Create a product\n      operationId: createProduct\n      requestBody:\n        description: Create a new prodcut in the store\n        content:\n          application\/json:\n            schema:\n              $ref: '#\/components\/schemas\/ProductDO'\n        required: true\n      security:\n        - ApiKey:\n            - Product.Create\n      responses:\n        '200':\n            description: OK\n            content:\n              application\/json:\n                schema:\n                  $ref: '#\/components\/schemas\/ProductDO'\ncomponents:\n  securitySchemes:\n    ApiKey:\n      type: apiKey\n      in: header\n      name: X-API-KEY\n  schemas:\n    ProductDO:\n      description: Product Information\n      type: object\n      properties:\n        id:\n          type: number\n          description: product id\n        name:\n          type: string\n          description: product name\n        price:\n          type: number\n          description: product price value<\/pre>\n<p>Copy the <code>products.yaml<\/code> file and create two versions so there are 3 files under <code>resources\\api folder<\/code>:<\/p>\n<p><span style=\"text-decoration: underline\"><em>api folder<\/em><\/span><\/p>\n<pre  class=\"brush:plain; wrap-lines:false\">C:\\MaryTools\\workspace\\JCG\\spring-boot-openapi\\src\\main\\resources\\api&gt;dir\n Volume in drive C is OS\n Volume Serial Number is 92BA-6AB7\n\n Directory of C:\\MaryTools\\workspace\\JCG\\spring-boot-openapi\\src\\main\\resources\\api\n\n05\/04\/2024  07:56 PM              .\n05\/03\/2024  09:13 PM              ..\n05\/04\/2024  07:55 PM             1,734 products.yaml\n05\/04\/2024  07:40 PM             1,765 products1.yaml\n05\/04\/2024  08:59 AM             1,786 products2.yaml\n               3 File(s)          5,285 bytes\n               2 Dir(s)  109,910,233,088 bytes free\n\nC:\\MaryTools\\workspace\\JCG\\spring-boot-openapi\\src\\main\\resources\\api&gt;<\/pre>\n<ul class=\"wp-block-list\">\n<li>The <code>products.yaml<\/code> has no vendor-specific extension, therefore can be generated without any customization.<\/li>\n<li>The <code>products1.yaml<\/code> file has a vendor-spec extension and sets the <code>x-spring-cacheable<\/code> as <code>true<\/code>.<\/li>\n<li>The <code>products2.yaml<\/code> file sets the <code>x-spring-cacheable<\/code> with the defined cache name.<\/li>\n<\/ul>\n<p>The difference between <code>products1.yaml<\/code> and <code>products2.yaml<\/code> is showing in the following screenshot:<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/admin.jpg\"><img decoding=\"async\" width=\"741\" height=\"461\" src=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/admin.jpg\" alt=\"\" class=\"wp-image-122292\" srcset=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/admin.jpg 741w, https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/admin-300x187.jpg 300w\" sizes=\"(max-width: 741px) 100vw, 741px\" \/><\/a><figcaption class=\"wp-element-caption\">Figure 2. Products.yaml x-spring-cacheable Difference<\/figcaption><\/figure>\n<\/div>\n<h2 class=\"wp-block-heading\">3. API Implementation<\/h2>\n<p>In this step, I will generate a spring boot server stub with the &#8220;<code>spring<\/code>&#8221; generator via its built-in template and create the implementation class and test the generated code with Junit tests.<\/p>\n<h3 class=\"wp-block-heading\">3.1 Create Spring Boot Application<\/h3>\n<p>Create a spring boot application and annotate with both <code>@SpringBootApplication<\/code> and <code>@EnableCaching<\/code>.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductApplication.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cache.annotation.EnableCaching;\n\n@SpringBootApplication\n@EnableCaching\npublic class ProductsApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(ProductsApplication.class, args);\n    }\n}\n<\/pre>\n<h3 class=\"wp-block-heading\">3.2 Create Implementation Classes<\/h3>\n<p>In this step, I will create an implementation class for the generated interface with mocked data.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductsApiImpl.java<\/em><\/span><\/p>\n<pre class=\"brush:java; highlight:[21,32]\">package com.zheng.demo.openapi.products.service;\n\nimport java.math.BigDecimal;\nimport java.util.Random;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Component;\n\nimport com.zheng.demo.openapi.products.api.ProductsApiDelegate;\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\n\n@Component\npublic class ProductsApiImpl implements ProductsApiDelegate {\n\tLogger logger = LoggerFactory.getLogger(ProductsApiImpl.class);\n\tprivate final Random rnd = new Random();\n\n\t@Override\n\tpublic ResponseEntity&lt;ProductDO&gt; getProduct(BigDecimal id) {\n\t\tlogger.info(\"getProduct called\");\n\t\tProductDO prod = new ProductDO();\n\t\tprod.setId(id);\n\t\tprod.setName(\"Product_\" + id);\n\t\tprod.setPrice(BigDecimal.valueOf(100.0 + rnd.nextDouble() * 100.0));\n\n\t\treturn ResponseEntity.ok(prod);\n\t}\n\n\t@Override\n\tpublic ResponseEntity&lt;ProductDO&gt; createProduct(ProductDO product) {\n\t\tlogger.info(\"createProduct called\");\n\t\tproduct.setId(BigDecimal.valueOf(rnd.nextDouble() * 10));\n\t\treturn ResponseEntity.ok(product);\n\n\t}\n}\n<\/pre>\n<ul class=\"wp-block-list\">\n<li>line 21, log a statement when <code>getProduct<\/code> service is called. It is used to verify if the cache is used or not.<\/li>\n<li>line 32, log a statement when the <code>createProduct<\/code> service is called.<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\"><a name=\"step33\"><\/a>3.3 Application.yaml <\/h3>\n<p>Set the logging application properties and the <code>base-path<\/code> properties. We use the log statements to verify if the cache data is used or not.<div style=\"display:inline-block; margin: 15px 0;\"> <div id=\"adngin-JavaCodeGeeks_incontent_video-0\" style=\"display:inline-block;\"><\/div> <\/div><\/p>\n<p><span style=\"text-decoration: underline\"><em>application.yaml<\/em><\/span><\/p>\n<pre class=\"brush:plain\">logging:\n  level:\n    root: INFO\n    org.springframework: INFO\nopenapi:\n  product:\n    base-path: v1<\/pre>\n<h2 class=\"wp-block-heading\"><a name=\"step4\"><\/a>4. Generated Source Code<\/h2>\n<p>Execute the <code>mvn generate-source<\/code> command and review the generated java source files.<\/p>\n<h3 class=\"wp-block-heading\">4.1 ApiUtil Class<\/h3>\n<p>The <code>ApiUtil<\/code> class has only one static method <code>setExampleResponse<\/code>.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ApiUtil.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products.api;\n\nimport org.springframework.web.context.request.NativeWebRequest;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic class ApiUtil {\n    public static void setExampleResponse(NativeWebRequest req, String contentType, String example) {\n        try {\n            HttpServletResponse res = req.getNativeResponse(HttpServletResponse.class);\n            res.setCharacterEncoding(\"UTF-8\");\n            res.addHeader(\"Content-Type\", contentType);\n            res.getWriter().print(example);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n<\/pre>\n<p><strong>Note<\/strong>: The <code>ApiUtil.java<\/code> file has the same content when generated from both built-in template and custom template.<\/p>\n<h3 class=\"wp-block-heading\">4.2 ProductsApi Interface<\/h3>\n<p>The <code>ProductsApi<\/code> interface includes 3 defaults methods: <\/p>\n<ul class=\"wp-block-list\">\n<li><code>getDelegate<\/code> is from the <code>delegatePattern<\/code> setting.<\/li>\n<li><code>createProduct<\/code> and <code>getProduct<\/code> are defined in the <code>Products.yaml<\/code> file under the <code>operation<\/code> section.<\/li>\n<\/ul>\n<p><span style=\"text-decoration: underline\"><em>ProductsApi.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">\/**\n * NOTE: This class is auto generated by OpenAPI Generator (https:\/\/openapi-generator.tech) (7.5.0).\n * https:\/\/openapi-generator.tech\n * Do not edit the class manually.\n *\/\npackage com.zheng.demo.openapi.products.api;\n\nimport java.math.BigDecimal;\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.*;\nimport java.util.List;\nimport java.util.Map;\nimport jakarta.annotation.Generated;\n\n@Generated(value = \"org.openapitools.codegen.languages.SpringCodegen\", date = \"2024-05-04T23:36:23.308233300-05:00[America\/Chicago]\", comments = \"Generator version: 7.5.0\")\n@Validated\npublic interface ProductsApi {\n\n    default ProductsApiDelegate getDelegate() {\n        return new ProductsApiDelegate() {};\n    }\n\n    \/**\n     * POST \/products : Create a product\n     *\n     * @param productDO Create a new prodcut in the store (required)\n     * @return OK (status code 200)\n     *\/\n    @RequestMapping(\n        method = RequestMethod.POST,\n        value = \"\/products\",\n        produces = { \"application\/json\" },\n        consumes = { \"application\/json\" }\n    )\n    \n    default ResponseEntity&lt;ProductDO&gt; createProduct(\n         @Valid @RequestBody ProductDO productDO\n    ) {\n        return getDelegate().createProduct(productDO);\n    }\n\n\n    \/**\n     * GET \/products\/{id} : Get product detail for a given product id\n     *\n     * @param id Product&#039;s identifier (required)\n     * @return OK (status code 200)\n     *\/\n    @RequestMapping(\n        method = RequestMethod.GET,\n        value = \"\/products\/{id}\",\n        produces = { \"application\/json\" }\n    )\n    \n    default ResponseEntity&lt;ProductDO&gt; getProduct(\n         @PathVariable(\"id\") BigDecimal id\n    ) {\n        return getDelegate().getProduct(id);\n    }\n\n}\n<\/pre>\n<p><strong>Note<\/strong>: The <code>ProductsApi<\/code> file has the same content when generated from both built-in template and custom template.<\/p>\n<h3 class=\"wp-block-heading\">4.3 ProductApiDelegate Interface<\/h3>\n<p>The <code>ProductApiDelegate<\/code> interface has the same methods as the <code>ProductApi<\/code> interface.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductsApiDelegate.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products.api;\n\nimport java.math.BigDecimal;\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.Valid;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport jakarta.annotation.Generated;\n\n\/**\n * A delegate to be called by the {@link ProductsApiController}}.\n * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class.\n *\/\n@Generated(value = \"org.openapitools.codegen.languages.SpringCodegen\", date = \"2024-05-04T23:36:23.308233300-05:00[America\/Chicago]\", comments = \"Generator version: 7.5.0\")\npublic interface ProductsApiDelegate {\n\n    default Optional&lt;NativeWebRequest&gt; getRequest() {\n        return Optional.empty();\n    }\n\n    \/**\n     * POST \/products : Create a product\n     *\n     * @param productDO Create a new prodcut in the store (required)\n     * @return OK (status code 200)\n     * @see ProductsApi#createProduct\n     *\/\n    default ResponseEntity&lt;ProductDO&gt; createProduct(ProductDO productDO) {\n        getRequest().ifPresent(request -&gt; {\n            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader(\"Accept\"))) {\n                if (mediaType.isCompatibleWith(MediaType.valueOf(\"application\/json\"))) {\n                    String exampleString = \"{ \\\"price\\\" : 6.027456183070403, \\\"name\\\" : \\\"name\\\", \\\"id\\\" : 0.8008281904610115 }\";\n                    ApiUtil.setExampleResponse(request, \"application\/json\", exampleString);\n                    break;\n                }\n            }\n        });\n        return new ResponseEntity&lt;&gt;(HttpStatus.NOT_IMPLEMENTED);\n\n    }\n\n    \/**\n     * GET \/products\/{id} : Get product detail for a given product id\n     *\n     * @param id Product&#039;s identifier (required)\n     * @return OK (status code 200)\n     * @see ProductsApi#getProduct\n     *\/\n    default ResponseEntity&lt;ProductDO&gt; getProduct(BigDecimal id) {\n        getRequest().ifPresent(request -&gt; {\n            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader(\"Accept\"))) {\n                if (mediaType.isCompatibleWith(MediaType.valueOf(\"application\/json\"))) {\n                    String exampleString = \"{ \\\"price\\\" : 6.027456183070403, \\\"name\\\" : \\\"name\\\", \\\"id\\\" : 0.8008281904610115 }\";\n                    ApiUtil.setExampleResponse(request, \"application\/json\", exampleString);\n                    break;\n                }\n            }\n        });\n        return new ResponseEntity&lt;&gt;(HttpStatus.NOT_IMPLEMENTED);\n\n    }\n\n}\n<\/pre>\n<p><strong>Note<\/strong>: The OpenAPI built-in template does not know how to transform the <code>x-spring-cacheable<\/code> extension, so developers must add the <code>@org.springframework.cache.annotation.Cacheable(\"default\")<\/code> annotation to the generated code in order to pass the integration test.<\/p>\n<h3 class=\"wp-block-heading\">4.4 ProductApiController Class<\/h3>\n<p>The generated <code>ProductsApiController<\/code> class has a &#8220;<code>openapi.product.base-path<\/code>&#8221; property. If the property is not defined, then it falls back to an empty string <code>\"\"<\/code>. Please refer to <a href=\"#step33\">step 3.3<\/a> as the <code>base-path<\/code> value is set to &#8220;<code>v1<\/code>&#8220;.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductsApiController.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products.api;\n\nimport java.math.BigDecimal;\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\n\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestHeader;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.CookieValue;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RequestPart;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.Valid;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.Generated;\n\n@Generated(value = \"org.openapitools.codegen.languages.SpringCodegen\", date = \"2024-05-04T07:47:53.432603-05:00[America\/Chicago]\")\n@Controller\n@RequestMapping(\"${openapi.product.base-path:}\")\npublic class ProductsApiController implements ProductsApi {\n\n    private final ProductsApiDelegate delegate;\n\n    public ProductsApiController(@Autowired(required = false) ProductsApiDelegate delegate) {\n        this.delegate = Optional.ofNullable(delegate).orElse(new ProductsApiDelegate() {});\n    }\n\n    @Override\n    public ProductsApiDelegate getDelegate() {\n        return delegate;\n    }\n\n}\n<\/pre>\n<p>Note: The <code>ProductsApiController<\/code> file has the same content when generated from both built-in template and custom template.<\/p>\n<h3 class=\"wp-block-heading\">4.5 ProductDO Class<\/h3>\n<p>The generated <code>ProductDO<\/code> class defines the <code>Product<\/code> data model.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductDO.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products.api.model;\n\nimport java.net.URI;\nimport java.util.Objects;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport java.math.BigDecimal;\nimport java.time.OffsetDateTime;\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.*;\n\n\nimport java.util.*;\nimport jakarta.annotation.Generated;\n\n\/**\n * Product Information\n *\/\n\n@Generated(value = \"org.openapitools.codegen.languages.SpringCodegen\", date = \"2024-05-04T23:36:23.308233300-05:00[America\/Chicago]\", comments = \"Generator version: 7.5.0\")\npublic class ProductDO {\n\n  private BigDecimal id;\n\n  private String name;\n\n  private BigDecimal price;\n\n  public ProductDO id(BigDecimal id) {\n    this.id = id;\n    return this;\n  }\n\n  \/**\n   * product id\n   * @return id\n  *\/\n  @Valid \n  @JsonProperty(\"id\")\n  public BigDecimal getId() {\n    return id;\n  }\n\n  public void setId(BigDecimal id) {\n    this.id = id;\n  }\n\n  public ProductDO name(String name) {\n    this.name = name;\n    return this;\n  }\n\n  \/**\n   * product name\n   * @return name\n  *\/\n  \n  @JsonProperty(\"name\")\n  public String getName() {\n    return name;\n  }\n\n  public void setName(String name) {\n    this.name = name;\n  }\n\n  public ProductDO price(BigDecimal price) {\n    this.price = price;\n    return this;\n  }\n\n  \/**\n   * product price value\n   * @return price\n  *\/\n  @Valid \n  @JsonProperty(\"price\")\n  public BigDecimal getPrice() {\n    return price;\n  }\n\n  public void setPrice(BigDecimal price) {\n    this.price = price;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    ProductDO productDO = (ProductDO) o;\n    return Objects.equals(this.id, productDO.id) &amp;&amp;\n        Objects.equals(this.name, productDO.name) &amp;&amp;\n        Objects.equals(this.price, productDO.price);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(id, name, price);\n  }\n\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder();\n    sb.append(\"class ProductDO {\\n\");\n    sb.append(\"    id: \").append(toIndentedString(id)).append(\"\\n\");\n    sb.append(\"    name: \").append(toIndentedString(name)).append(\"\\n\");\n    sb.append(\"    price: \").append(toIndentedString(price)).append(\"\\n\");\n    sb.append(\"}\");\n    return sb.toString();\n  }\n\n  \/**\n   * Convert the given object to string with each line indented by 4 spaces\n   * (except the first line).\n   *\/\n  private String toIndentedString(Object o) {\n    if (o == null) {\n      return \"null\";\n    }\n    return o.toString().replace(\"\\n\", \"\\n    \");\n  }\n}\n\n<\/pre>\n<h2 class=\"wp-block-heading\"><a name=\"step5\"><\/a>5. Junit Test Classes<\/h2>\n<h3 class=\"wp-block-heading\">5.1 Unit Test<\/h3>\n<p><code>ProductsApiImplUnitTest<\/code> has 2 tests which test both <code>getProduct<\/code> and <code>createProduct<\/code> services.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductsApiImplUnitTest.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products.service;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.ResponseEntity;\n\nimport com.zheng.demo.openapi.products.api.ProductsApi;\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\n\n@SpringBootTest\nclass ProductsApiImplUnitTest {\n\n\t@Autowired\n\tprivate ProductsApi api;\n\n\t@Test\n\tvoid whenGetProduct_then_success() {\n\n\t\tResponseEntity&lt;ProductDO&gt; response = api.getProduct(new BigDecimal(1));\n\t\tassertThat(response).isNotNull();\n\n\t\tassertThat(response.getStatusCode().is2xxSuccessful()).isTrue();\n\n\t}\n\n\t@Test\n\tvoid whenCreateProduct_then_success() {\n\t\tProductDO product = new ProductDO();\n\t\tproduct.setName(\"Test\");\n\t\tproduct.setPrice(new BigDecimal(100));\n\t\tResponseEntity&lt;ProductDO&gt; response = api.createProduct(product);\n\t\tassertThat(response).isNotNull();\n\n\t\tassertThat(response.getStatusCode().is2xxSuccessful()).isTrue();\n\n\t}\n\n}<\/pre>\n<p>Execute the junit tests and both passed as expected.<\/p>\n<h3 class=\"wp-block-heading\">5.2 Integration Test<\/h3>\n<p><code>ProductsApplicationIntegrationTest<\/code> has 3 tests which test both <code>getProduct<\/code> and <code>createProduct<\/code> at the test server. The <code>getProduct<\/code> should use cache for the same product id.<\/p>\n<p><span style=\"text-decoration: underline\"><em>ProductsApplicationIntegrationTest.java<\/em><\/span><\/p>\n<pre class=\"brush:java\">package com.zheng.demo.openapi.products;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.web.client.TestRestTemplate;\nimport org.springframework.boot.test.web.server.LocalServerPort;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\n\nimport com.zheng.demo.openapi.products.api.model.ProductDO;\n\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\nclass ProductsApplicationIntegrationTest {\n\n\t@LocalServerPort\n\tprivate int port;\n\n\t@Autowired\n\tprivate TestRestTemplate restTemplate;\n\n\t@Test\n\tvoid whenGetProduct_thenSuccess() {\n\t\tResponseEntity&lt;ProductDO&gt; response = restTemplate.getForEntity(\"http:\/\/localhost:\" + port + \"\/v1\/products\/1\",\n\t\t\t\tProductDO.class);\n\t\tassertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);\n\t}\n\n\t@Test\n\tvoid whenGetProductMultipleTimes_thenResponseCached() {\n\n\t\t\/\/ Call server a few times and collect responses\n\t\tvar quotes = IntStream.range(1, 10).boxed()\n\t\t\t\t.map((i) -&gt; restTemplate.getForEntity(\"http:\/\/localhost:\" + port + \"\/v1\/products\/1\", ProductDO.class))\n\t\t\t\t.map(HttpEntity::getBody).collect(Collectors.groupingBy((q -&gt; q.hashCode()), Collectors.counting()));\n\n\t\tassertThat(quotes.size()).isEqualTo(1);\n\t}\n\n\t@Test\n\tvoid whenCreateProduct_thenSuccess() {\n\t\tProductDO product = new ProductDO();\n\t\tproduct.setName(\"TEST\");\n\t\tResponseEntity&lt;ProductDO&gt; response = restTemplate.postForEntity(\"http:\/\/localhost:\" + port + \"\/v1\/products\",\n\t\t\t\tproduct, ProductDO.class);\n\n\t\tassertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);\n\t}\n}<\/pre>\n<p>The <code>whenGetProductMultipleTimes_thenResponseCached<\/code> test failed as the <code>getProduct<\/code> service was called multiple times.<\/p>\n<pre class=\"brush:plain\">2024-05-04T23:28:54.048-05:00  INFO 36452 --- [o-auto-1-exec-2] c.z.d.o.p.service.ProductsApiImpl        : createProduct called\n2024-05-04T23:28:54.150-05:00  INFO 36452 --- [o-auto-1-exec-5] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.164-05:00  INFO 36452 --- [o-auto-1-exec-4] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.168-05:00  INFO 36452 --- [o-auto-1-exec-1] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.173-05:00  INFO 36452 --- [o-auto-1-exec-3] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.178-05:00  INFO 36452 --- [o-auto-1-exec-6] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.181-05:00  INFO 36452 --- [o-auto-1-exec-7] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.185-05:00  INFO 36452 --- [o-auto-1-exec-8] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.189-05:00  INFO 36452 --- [o-auto-1-exec-9] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.193-05:00  INFO 36452 --- [-auto-1-exec-10] c.z.d.o.p.service.ProductsApiImpl        : getProduct called\n2024-05-04T23:28:54.196-05:00  INFO 36452 --- [o-auto-1-exec-2] c.z.d.o.p.service.ProductsApiImpl        : getProduct called<\/pre>\n<p><strong>Note<\/strong>: the <code>x-spring-cacheable<\/code> is not supported by the default template, therefore the <code>getProduct<\/code> service is called 10 times instead of one time.<\/p>\n<h2 class=\"wp-block-heading\">6. Spring Boot OpenAPI Generator Custom Templates<\/h2>\n<p>As seen in <a href=\"#step5\">step 5<\/a>, the generated spring boot server stub source code missed the cache annotation. In this step, I will update the built-in <code><a href=\"https:\/\/github.com\/OpenAPITools\/openapi-generator\/blob\/master\/modules\/openapi-generator\/src\/main\/resources\/JavaSpring\/apiDelegate.mustache\" target=\"_blank\" rel=\"noreferrer noopener\">apiDelegate.mustache<\/a><\/code> template to add the &#8220;<code>x-spring-cacheable<\/code>&#8221; vendor extension and use it when generating the code from the <code>products1.yaml<\/code>.<\/p>\n<pre class=\"brush:java; highlight:[73,74,75]\">\/*\n* Generated code: do not modify !\n* Custom template with support for x-spring-cacheable extension\n*\/\npackage {{package}};\n\n{{#imports}}import {{import}};\n{{\/imports}}\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\n{{#useResponseEntity}}\n    import org.springframework.http.ResponseEntity;\n{{\/useResponseEntity}}\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.multipart.MultipartFile;\n{{#reactive}}\n    import org.springframework.web.server.ServerWebExchange;\n    import reactor.core.publisher.Flux;\n    import reactor.core.publisher.Mono;\n    import org.springframework.http.codec.multipart.Part;\n{{\/reactive}}\n\n{{#useBeanValidation}}\n    import {{javaxPackage}}.validation.constraints.*;\n    import {{javaxPackage}}.validation.Valid;\n{{\/useBeanValidation}}\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n{{#async}}\n    import java.util.concurrent.CompletableFuture;\n{{\/async}}\nimport {{javaxPackage}}.annotation.Generated;\n\n{{#operations}}\n    \/**\n    * A delegate to be called by the {@link {{classname}}Controller}}.\n    * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class.\n    *\/\n    {{&gt;generatedAnnotation}}\n    public interface {{classname}}Delegate {\n    {{#jdk8-default-interface}}\n\n        default Optional&lt;NativeWebRequest&gt; getRequest() {\n            return Optional.empty();\n            }\n    {{\/jdk8-default-interface}}\n\n    {{#operation}}\n            \/**\n            * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{\/summary}}\n        {{#notes}}\n                * {{.}}\n        {{\/notes}}\n            *\n        {{#allParams}}\n                * @param {{paramName}} {{description}}{{#required}} (required){{\/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{\/defaultValue}}){{\/required}}\n        {{\/allParams}}\n            * @return {{#responses}}{{message}} (status code {{code}}){{^-last}}\n                *         or {{\/-last}}{{\/responses}}\n        {{#isDeprecated}}\n                * @deprecated\n        {{\/isDeprecated}}\n        {{#externalDocs}}\n                * {{description}}\n                * @see &lt;a href=\"{{url}}\"&gt;{{summary}} Documentation&lt;\/a&gt;\n        {{\/externalDocs}}\n            * @see {{classname}}#{{operationId}}\n            *\/\n        {{#isDeprecated}}\n                @Deprecated\n        {{\/isDeprecated}}\n        {{#vendorExtensions.x-spring-cacheable}}\n        @org.springframework.cache.annotation.Cacheable({{#name}}\"{{.}}\"{{\/name}}{{^name}}\"default\"{{\/name}})\n        {{\/vendorExtensions.x-spring-cacheable}}\n        {{#jdk8-default-interface}}default {{\/jdk8-default-interface}}{{&gt;responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{&gt;optionalDataType}}{{\/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{\/reactive}}{{#reactive}}{{^isArray}}Mono&lt;{{{dataType}}}&gt;{{\/isArray}}{{#isArray}}Flux&lt;{{{baseType}}}&gt;{{\/isArray}}{{\/reactive}}{{\/isBodyParam}}{{\/isFile}}{{#isFile}}{{#isArray}}List&lt;{{\/isArray}}{{#reactive}}Flux&lt;Part&gt;{{\/reactive}}{{^reactive}}MultipartFile{{\/reactive}}{{#isArray}}&gt;{{\/isArray}}{{\/isFile}} {{paramName}}{{^-last}},\n        {{\/-last}}{{\/allParams}}{{#reactive}}{{#hasParams}},\n        {{\/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}} {\n        {{&gt;methodBody}}\n            }{{\/jdk8-default-interface}}\n\n    {{\/operation}}\n        }\n{{\/operations}}\n<\/pre>\n<ul class=\"wp-block-list\">\n<li>line 73-75: support the <code>x-spring-cacheable<\/code> vendor-specific extension.<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\">6.1 Use Products1.yaml<\/h3>\n<p>Update the <code>pom.xml<\/code> to specify <code>templateResourcePath<\/code> with the custom template and use <code>products1.xml<\/code>. As you seen at <a href=\"#step2\">step 2<\/a>, the <code>products1.yaml<\/code> includes <code>x-spring-cacheable: true<\/code><\/p>\n<p><span style=\"text-decoration: underline\"><em>Updated pom.xml at the configuration section<\/em><\/span><\/p>\n<pre class=\"brush:xml; highlight:[7]\">&lt;configuration&gt;\n\t\t\t\t\t\t\t&lt;inputSpec&gt;\n\t\t\t\t\t\t\t\t${project.basedir}\/src\/main\/resources\/api\/products1.yaml&lt;\/inputSpec&gt;\n\t\t\t\t\t\t\t&lt;generatorName&gt;spring&lt;\/generatorName&gt;\n\t\t\t\t\t\t\t&lt;supportingFilesToGenerate&gt;ApiUtil.java&lt;\/supportingFilesToGenerate&gt;\n\t\t\t\t\t\t\t&lt;templateResourcePath&gt;\n\t\t\t\t\t\t\t\t${project.basedir}\/src\/main\/resources\/templates\/JavaSpring\n\t\t\t\t\t\t\t&lt;\/templateResourcePath&gt;\n\t\t\t\t\t\t\t&lt;globalProperties&gt;\n\t\t\t\t\t\t\t\t&lt;debugOpenAPI&gt;true&lt;\/debugOpenAPI&gt;\n\t\t\t\t\t\t\t&lt;\/globalProperties&gt;\n\t\t\t\t\t\t\t&lt;configOptions&gt;\n\t\t\t\t\t\t\t\t&lt;delegatePattern&gt;true&lt;\/delegatePattern&gt;\n\t\t\t\t\t\t\t\t&lt;apiPackage&gt;com.zheng.demo.openapi.products.api&lt;\/apiPackage&gt;\n\t\t\t\t\t\t\t\t&lt;modelPackage&gt;\n\t\t\t\t\t\t\t\t\tcom.zheng.demo.openapi.products.api.model&lt;\/modelPackage&gt;\n\t\t\t\t\t\t\t\t&lt;documentationProvider&gt;source&lt;\/documentationProvider&gt;\n\t\t\t\t\t\t\t\t&lt;dateLibrary&gt;java8&lt;\/dateLibrary&gt;\n\t\t\t\t\t\t\t\t&lt;openApiNullable&gt;false&lt;\/openApiNullable&gt;\n\t\t\t\t\t\t\t\t&lt;useJakartaEe&gt;true&lt;\/useJakartaEe&gt;\n\t\t\t\t\t\t\t\t&lt;useSpringBoot3&gt;true&lt;\/useSpringBoot3&gt;\n\t\t\t\t\t\t\t&lt;\/configOptions&gt;<\/pre>\n<p><strong>Note<\/strong>: line 7 specifies the <code>templateResourcePath<\/code> file location.<\/p>\n<h3 class=\"wp-block-heading\">6.2 Re-generate the Source<\/h3>\n<p>Run the <code>mvn generate-source<\/code> command and it will generate five files as the <a href=\"#step4\">step 4<\/a>. All files have the same content except <code>ProductsApiDelegate<\/code> now added <code>@org.springframework.cache.annotation.Cacheable(\"default\")<\/code> to the getProduct method.<\/p>\n<p><code>ProductsApiDelegate.java<\/code>&#8216;s <code>getProduct<\/code> method<\/p>\n<pre class=\"brush:java; highlight:[1]\">  @org.springframework.cache.annotation.Cacheable(\"default\")\n        default ResponseEntity&lt;ProductDO&gt; getProduct(BigDecimal id) {\n        getRequest().ifPresent(request -&gt; {\n            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader(\"Accept\"))) {\n                if (mediaType.isCompatibleWith(MediaType.valueOf(\"application\/json\"))) {\n                    String exampleString = \"{ \\\"price\\\" : 6.027456183070403, \\\"name\\\" : \\\"name\\\", \\\"id\\\" : 0.8008281904610115 }\";\n                    ApiUtil.setExampleResponse(request, \"application\/json\", exampleString);\n                    break;\n                }\n            }\n        });\n        return new ResponseEntity&lt;&gt;(HttpStatus.NOT_IMPLEMENTED);\n\n            }<\/pre>\n<p><strong>Note<\/strong>: line 1, the <code>@org.springframework.cache.annotation.Cacheable(\"default\")<\/code> is added.<\/p>\n<h2 class=\"wp-block-heading\">7. Using the Modified Template<\/h2>\n<p>In this step, I will update the <code>pom.xml<\/code> to specify the template file and then re-generate the source code. this time, it will include the <code>@cacheable<\/code> annotation.<\/p>\n<h3 class=\"wp-block-heading\">7.1 Use Products2.yaml<\/h3>\n<p>Update the <code>pom.xml<\/code> at the <code>inputSpec<\/code> to use <code>products2.yaml<\/code>. Refer to <a href=\"#step2\">step 2<\/a>, <code>products2.yaml<\/code> specifies the <code>x-spring-cacheable: name: get-product<\/code><\/p>\n<h3 class=\"wp-block-heading\">7.2 Re-generate the Source<\/h3>\n<pre class=\"brush:java; highlight:[1]\">  @org.springframework.cache.annotation.Cacheable(\"get-product\")\n        default ResponseEntity&lt;ProductDO&gt; getProduct(BigDecimal id) {\n        getRequest().ifPresent(request -&gt; {\n            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader(\"Accept\"))) {\n                if (mediaType.isCompatibleWith(MediaType.valueOf(\"application\/json\"))) {\n                    String exampleString = \"{ \\\"price\\\" : 6.027456183070403, \\\"name\\\" : \\\"name\\\", \\\"id\\\" : 0.8008281904610115 }\";\n                    ApiUtil.setExampleResponse(request, \"application\/json\", exampleString);\n                    break;\n                }\n            }\n        });\n        return new ResponseEntity&lt;&gt;(HttpStatus.NOT_IMPLEMENTED);\n\n            }<\/pre>\n<p><strong>Note<\/strong>: line 1, the <code>@org.springframework.cache.annotation.Cacheable(\"get-product\")<\/code> is added.<\/p>\n<p>Run tests and now all passed as the cache is used.<\/p>\n<h2 class=\"wp-block-heading\">8. Conclusion<\/h2>\n<p>In this tutorial, I demonstrated&nbsp;how to configure the OpenAPI Generator tool in a spring&nbsp;boot maven project and generate source code from an open API specification&nbsp;with a custom template that supports a simple vendor extension. The spring boot openapi generator custom templates are used in the following steps.<\/p>\n<ul class=\"wp-block-list\">\n<li>Execute the maven <code>generate-source<\/code> command to generate source code from the <code>products.yaml<\/code> file.<\/li>\n<li>Update the <code>products.yaml<\/code> file with the &#8220;<code>x-spring-cacheable<\/code>&#8221; vendor-specific extension with the default setting and generate from a custom template.<\/li>\n<li>Update the <code>products.yaml<\/code> file with the &#8220;<code>x-spring-cacheable<\/code>&#8221; vendor-specific extension and set the cache name and generate from a custom template.<\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">9. Download<\/h2>\n<p>This was an example of Spring boot maven project which generates source code from a custom template.<\/p>\n<div class=\"download\"><strong>Download<\/strong><br \/>\nYou can download the full source code of this example here: <a href=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/spring-boot-openapi.zip\"><strong>OpenAPI Generator Custom Templates<\/strong><\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip;<\/p>\n","protected":false},"author":128892,"featured_media":122380,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[2034,854,2205],"class_list":["post-122156","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-enterprise-java","tag-openapi","tag-spring-boot","tag-spring-boot-3-0"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>OpenAPI Generator Custom Templates - Java Code Geeks<\/title>\n<meta name=\"description\" content=\"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"OpenAPI Generator Custom Templates - Java Code Geeks\" \/>\n<meta property=\"og:description\" content=\"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html\" \/>\n<meta property=\"og:site_name\" content=\"Java Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/javacodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2024-05-06T10:14:37+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-05-06T10:19:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Mary Zheng\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@javacodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@javacodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mary Zheng\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html\"},\"author\":{\"name\":\"Mary Zheng\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/33e795ab61de7fab61ed89b4de1668f5\"},\"headline\":\"OpenAPI Generator Custom Templates\",\"datePublished\":\"2024-05-06T10:14:37+00:00\",\"dateModified\":\"2024-05-06T10:19:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html\"},\"wordCount\":1043,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/open-api-logo.jpg\",\"keywords\":[\"OpenAPI\",\"Spring Boot\",\"Spring Boot 3.0\"],\"articleSection\":[\"Enterprise Java\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html\",\"name\":\"OpenAPI Generator Custom Templates - Java Code Geeks\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/open-api-logo.jpg\",\"datePublished\":\"2024-05-06T10:14:37+00:00\",\"dateModified\":\"2024-05-06T10:19:10+00:00\",\"description\":\"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#primaryimage\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/open-api-logo.jpg\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/open-api-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/openapi-generator-custom-templates.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Enterprise Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\\\/enterprise-java\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"OpenAPI Generator Custom Templates\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"name\":\"Java Code Geeks\",\"description\":\"Java Developers Resource Center\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"alternateName\":\"JCG\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.javacodegeeks.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/javacodegeeks\",\"https:\\\/\\\/x.com\\\/javacodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/33e795ab61de7fab61ed89b4de1668f5\",\"name\":\"Mary Zheng\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/04\\\/cropped-Mary-Zheng-96x96.jpg\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/04\\\/cropped-Mary-Zheng-96x96.jpg\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2024\\\/04\\\/cropped-Mary-Zheng-96x96.jpg\",\"caption\":\"Mary Zheng\"},\"description\":\"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.\",\"sameAs\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/\"],\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/author\\\/mary-zheng\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"OpenAPI Generator Custom Templates - Java Code Geeks","description":"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html","og_locale":"en_US","og_type":"article","og_title":"OpenAPI Generator Custom Templates - Java Code Geeks","og_description":"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!","og_url":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html","og_site_name":"Java Code Geeks","article_publisher":"https:\/\/www.facebook.com\/javacodegeeks","article_published_time":"2024-05-06T10:14:37+00:00","article_modified_time":"2024-05-06T10:19:10+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg","type":"image\/jpeg"}],"author":"Mary Zheng","twitter_card":"summary_large_image","twitter_creator":"@javacodegeeks","twitter_site":"@javacodegeeks","twitter_misc":{"Written by":"Mary Zheng","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#article","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html"},"author":{"name":"Mary Zheng","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/33e795ab61de7fab61ed89b4de1668f5"},"headline":"OpenAPI Generator Custom Templates","datePublished":"2024-05-06T10:14:37+00:00","dateModified":"2024-05-06T10:19:10+00:00","mainEntityOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html"},"wordCount":1043,"commentCount":0,"publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg","keywords":["OpenAPI","Spring Boot","Spring Boot 3.0"],"articleSection":["Enterprise Java"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html","url":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html","name":"OpenAPI Generator Custom Templates - Java Code Geeks","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#primaryimage"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg","datePublished":"2024-05-06T10:14:37+00:00","dateModified":"2024-05-06T10:19:10+00:00","description":"Interested to learn more about openapi generator custom templates? Then check out our detailed example on OpenAPI Generator Custom Templates!","breadcrumb":{"@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#primaryimage","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/05\/open-api-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.javacodegeeks.com\/openapi-generator-custom-templates.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.javacodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Java","item":"https:\/\/www.javacodegeeks.com\/category\/java"},{"@type":"ListItem","position":3,"name":"Enterprise Java","item":"https:\/\/www.javacodegeeks.com\/category\/java\/enterprise-java"},{"@type":"ListItem","position":4,"name":"OpenAPI Generator Custom Templates"}]},{"@type":"WebSite","@id":"https:\/\/www.javacodegeeks.com\/#website","url":"https:\/\/www.javacodegeeks.com\/","name":"Java Code Geeks","description":"Java Developers Resource Center","publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"alternateName":"JCG","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.javacodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.javacodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.javacodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/javacodegeeks","https:\/\/x.com\/javacodegeeks"]},{"@type":"Person","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/33e795ab61de7fab61ed89b4de1668f5","name":"Mary Zheng","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/04\/cropped-Mary-Zheng-96x96.jpg","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/04\/cropped-Mary-Zheng-96x96.jpg","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2024\/04\/cropped-Mary-Zheng-96x96.jpg","caption":"Mary Zheng"},"description":"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.","sameAs":["https:\/\/www.javacodegeeks.com\/"],"url":"https:\/\/www.javacodegeeks.com\/author\/mary-zheng"}]}},"_links":{"self":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/122156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/users\/128892"}],"replies":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/comments?post=122156"}],"version-history":[{"count":0,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/122156\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media\/122380"}],"wp:attachment":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media?parent=122156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/categories?post=122156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/tags?post=122156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}