{"id":14986677,"url":"https://github.com/sigpwned/openapi-generator-maven-plugin-template-customization-example","last_synced_at":"2025-08-10T06:43:50.046Z","repository":{"id":37819767,"uuid":"479100490","full_name":"sigpwned/openapi-generator-maven-plugin-template-customization-example","owner":"sigpwned","description":"An example Maven project that uses the OpenAPI Generator Maven plugin with template customization to generate an API model and server","archived":false,"fork":false,"pushed_at":"2023-04-24T11:03:11.000Z","size":49,"stargazers_count":9,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T22:51:36.330Z","etag":null,"topics":["codegen","codegeneration","maven","openapi","openapi-codegen","openapi-generator","openapi-generator-maven-plugin","openapi-spec","sscce"],"latest_commit_sha":null,"homepage":"","language":"Mustache","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sigpwned.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-07T18:17:07.000Z","updated_at":"2024-11-10T06:15:46.000Z","dependencies_parsed_at":"2024-09-25T00:31:29.001Z","dependency_job_id":null,"html_url":"https://github.com/sigpwned/openapi-generator-maven-plugin-template-customization-example","commit_stats":{"total_commits":19,"total_committers":2,"mean_commits":9.5,"dds":"0.21052631578947367","last_synced_commit":"045401e1e3a6713fd7451e1cb2bc6925e72f0fc4"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sigpwned/openapi-generator-maven-plugin-template-customization-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sigpwned%2Fopenapi-generator-maven-plugin-template-customization-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sigpwned%2Fopenapi-generator-maven-plugin-template-customization-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sigpwned%2Fopenapi-generator-maven-plugin-template-customization-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sigpwned%2Fopenapi-generator-maven-plugin-template-customization-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sigpwned","download_url":"https://codeload.github.com/sigpwned/openapi-generator-maven-plugin-template-customization-example/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sigpwned%2Fopenapi-generator-maven-plugin-template-customization-example/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269688010,"owners_count":24459398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegen","codegeneration","maven","openapi","openapi-codegen","openapi-generator","openapi-generator-maven-plugin","openapi-spec","sscce"],"created_at":"2024-09-24T14:13:19.540Z","updated_at":"2025-08-10T06:43:50.003Z","avatar_url":"https://github.com/sigpwned.png","language":"Mustache","readme":"# openapi-generator-maven-plugin-template-customization-example [![codegen](https://github.com/sigpwned/openapi-generator-maven-plugin-template-customization-example/actions/workflows/codegen.yml/badge.svg)](https://github.com/sigpwned/openapi-generator-maven-plugin-template-customization-example/actions/workflows/codegen.yml)\n\n## Motivation\n\nThe [OpenAPI\nGenerator](https://github.com/OpenAPITools/openapi-generator) is a\nmarvelous piece of technology. It allows users to generate model,\ninterface, and implementation code for server and client for a variety\nof platforms from an OpenAPI spec quickly and easily. At least, for\ndefault configurations.\n\nBut sometimes users need to do something outside of the\ndefault. Fortunately, the project offers rich capabilities for\n[template](https://openapi-generator.tech/docs/templating) and\n[ad-hoc](https://openapi-generator.tech/docs/customization)\ncustomization. Unfortunately, those customization features can be\ndifficult to set up and use in practice.\n\nThis project is a [SSCCE](http://sscce.org/) for performing template\ncustomization using the OpenAPI Generator Maven Plugin. The chosen\ncustomization uses\n[extensions](https://openapi-generator.tech/docs/templating/#extensions)\nto add per-method support for JAX-RS 2.0 `@Suspended AsyncResponse`\nfeatures to the\n[`jaxrs-spec`](https://openapi-generator.tech/docs/generators/jaxrs-spec)\ngenerator. If anyone happens to need `AsyncResponse` code generation\nfor the OpenAPI Generator Maven Plugin, this should work out of the\nbox.\n\nThis project is also a fine starting point for a Reasonable Default of\nusing the OpenAPI Generator Maven Plugin, for anyone looking for an\nexample to start from.\n\n## Introduction\n\nLet's dive into the particulars of this example.\n\n### OpenAPI Spec\n\nThe OpenAPI spec for this example is very simple:\n\n    openapi: 3.0.2\n    info:\n      title: OpenAPI Generator Maven Plugin Template Customization Example\n      description: An example Maven project that uses the OpenAPI Generator Maven plugin with template customization to generate an API model and server.\n      contact:\n        email: andy.boothe@gmail.com\n      version: 0.0.0\n    servers:\n      - url: http://localhost:8080/v1\n    tags:\n      - name: example\n        description: Example endpoint\n    paths:\n      /greet:\n        post:\n          tags:\n            - example\n          summary: Greet the user\n          description: Generate a greeting for the given name\n          operationId: greet\n          # We can set determine whether each method is synchronous or asynchronous using this flag.\n          # This particular option is an extension, so you won't find documentation for it anywhere.\n          # Implementing it is the point of this exercise.\n          x-async-enabled: false\n          requestBody:\n            description: The name to greet\n            content:\n              application/json:\n                schema:\n                  $ref: '#/components/schemas/Name'\n          responses:\n            200:\n              description: The greeting was generated successfully\n              content:\n                application/json:\n                  schema:\n                    $ref: '#/components/schemas/Greeting'\n    components:\n      schemas:\n        Name:\n          type: object\n          properties:\n            name:\n              type: string\n    \n        Greeting:\n          type: object\n          properties:\n            greeting:\n              type: string\n\nThere is one endpoint, which generates a greeting given a name, and\none model object each for a Name and a Greeting. We'll be looking at\ncode generated for this spec a little later on.\n\n### POM\n\nNaturally, the OpenAPI Generator Maven Plugin is configured using the\nPOM. This project uses a reasonably simple and sane configuration of\nthe plugin:\n\n    \u003cconfiguration\u003e\n        \u003c!-- This section includes the \"global\" configuration options, so called because they apply to all generators. Users generally don't need to care about this dxcept to make sure their configuration information lands in the correct place - \u003cconfiguration\u003e vs \u003cconfigOptions\u003e. --\u003e\n        \u003c!-- https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin --\u003e\n    \n        \u003c!-- The OpenAPI spec is in a file in this repository called \"openapi.yml\". It can also be a hyperlink. --\u003e\n        \u003cinputSpec\u003eopenapi.yml\u003c/inputSpec\u003e\n    \n        \u003c!-- There are many generators available. We are using jaxrs-spec. --\u003e\n        \u003cgeneratorName\u003ejaxrs-spec\u003c/generatorName\u003e\n    \n        \u003c!-- APIs are service interfaces or implementations. --\u003e\n        \u003cgenerateApis\u003etrue\u003c/generateApis\u003e\n    \n        \u003c!-- Models are DTOs, generally from the schemas portion of the spec. --\u003e\n        \u003cgenerateModels\u003etrue\u003c/generateModels\u003e\n    \n        \u003c!-- In this example, we're not generating the POM, README, etc. Just the code, ma'am. --\u003e\n        \u003cgenerateSupportingFiles\u003efalse\u003c/generateSupportingFiles\u003e\n    \n        \u003c!-- We are showing off template customization today. The custom templates are stored here. --\u003e\n        \u003ctemplateDirectory\u003e${project.basedir}/openapi/templates\u003c/templateDirectory\u003e\n    \n        \u003c!-- This is the director where generated code will be stored. The plugin automatically adds this directory to your build. This is the default value. You generally don't need to change this, but it's worth calling out in this example so users know where to look for generated code. --\u003e\n        \u003coutput\u003e${project.build.directory}/generated-sources/openapi\u003c/output\u003e\n    \n        \u003cconfigOptions\u003e\n            \u003c!-- These are the \"local\" configuration options that apply only to this generator. --\u003e\n            \u003c!-- https://openapi-generator.tech/docs/generators/jaxrs-spec --\u003e\n    \n            \u003c!-- These are the packages we want our APIs and Models generated in, respectively. --\u003e\n            \u003capiPackage\u003ecom.sigpwned.greeting.spi.service\u003c/apiPackage\u003e\n            \u003cmodelPackage\u003ecom.sigpwned.greeting.spi.model\u003c/modelPackage\u003e\n    \n            \u003c!-- I personally consider these to be best practices. However, they are up to you! --\u003e\n            \u003cdateLibrary\u003ejava8\u003c/dateLibrary\u003e\n            \u003cuseSwaggerAnnotations\u003efalse\u003c/useSwaggerAnnotations\u003e\n    \n            \u003c!-- These values are largely up to you, and depend on your goals and philosophy. This SSCCE should support all permutations of the below. --\u003e\n    \n            \u003c!-- I prefer to have interface methods return model objects, not JAX-RS Response objects. I can still generate any response I want by throwing WebApplicationException classes. --\u003e\n            \u003c!-- If this is set to true, then all generated service methods will return javax.ws.rs.core.Response objects. --\u003e\n            \u003c!-- If this is set to false, then all generated service methods will return their respective model objects. --\u003e\n            \u003creturnResponse\u003efalse\u003c/returnResponse\u003e\n    \n            \u003c!-- I prefer to generate interfaces, not implementation. This allows me to make sure that my client and server implementations are in sync. --\u003e\n            \u003c!-- If this is set to true, then service interfaces will be generated. --\u003e\n            \u003c!-- If this is set to false, then service implementations will be generated. --\u003e\n            \u003cinterfaceOnly\u003etrue\u003c/interfaceOnly\u003e\n    \n            \u003c!-- This is a useful feature for web frameworks that support the JAX-RS 2.1 feature of implementing asynchronous processing by returning CompletionStage instances. In all cases, generated service methods return either Response or a model object, depending on the above configuration. This configuration option affects all methods. Users cannot choose to make individual methods synchronous versus asynchronous.  --\u003e\n            \u003c!-- If this is set to true, then all generated service method return types will be changed from T to CompletionStage\u003cT\u003e. --\u003e\n            \u003c!-- If this is set to false, then all generate service method return types will be unchanged. --\u003e\n            \u003csupportAsync\u003efalse\u003c/supportAsync\u003e\n        \u003c/configOptions\u003e\n    \u003c/configuration\u003e\n\nThe `\u003cconfiguration\u003e` (as opposed to `\u003cconfigOptions\u003e`) portion of\nthis project is largely boilerplate, and won't change much even if you\nswitch generators. The `\u003cconfigOptions\u003e` section is much more tailored\nto the task. In the default example, note that we are generating\ninterfaces that return model objects synchronously.\n\n## Default Behavior\n\nBefore we dive into template customization, let's understand the\ngenerator's out-of-the-box functionality first. Some of this is\nspecific to the `jaxrs-spec` generator, but much of it is universal to\nall generators.\n\n### Running the build\n\nLet's run the maven build with this default configuration. This\nproduces the following output:\n\n    $ mvn clean compile\n    [INFO] Scanning for projects...\n    [INFO] \n    [INFO] --\u003c com.sigpwned:openapi-generator-maven-plugin-template-customization-example \u003e--\n    [INFO] Building openapi-generator-maven-plugin-template-customization-example 0.0.0-SNAPSHOT\n    [INFO] --------------------------------[ pom ]---------------------------------\n    [INFO] \n    [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ openapi-generator-maven-plugin-template-customization-example ---\n    [INFO] Deleting /Users/aboothe/eclipse-workspace/openapi-generator-maven-plugin-template-customization-example/target\n    [INFO] \n    [INFO] --- openapi-generator-maven-plugin:5.4.0:generate (generate) @ openapi-generator-maven-plugin-template-customization-example ---\n    [INFO] Generating with dryRun=false\n    [INFO] Output directory (/Users/aboothe/eclipse-workspace/openapi-generator-maven-plugin-template-customization-example/target/generated-sources/openapi) does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated.\n    [INFO] OpenAPI Generator: jaxrs-spec (server)\n    [INFO] Generator 'jaxrs-spec' is considered stable.\n    [INFO] Environment variable JAVA_POST_PROCESS_FILE not defined so the Java code may not be properly formatted. To define it, try 'export JAVA_POST_PROCESS_FILE=\"/usr/local/bin/clang-format -i\"' (Linux/Mac)\n    [INFO] NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).\n    [INFO] Invoker Package Name, originally not set, is now derived from api package name: com.sigpwned.greeting.spi\n    [INFO] Processing operation greet\n    [INFO] writing file /Users/aboothe/eclipse-workspace/openapi-generator-maven-plugin-template-customization-example/target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Greeting.java\n    [INFO] writing file /Users/aboothe/eclipse-workspace/openapi-generator-maven-plugin-template-customization-example/target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Name.java\n    [INFO] writing file /Users/aboothe/eclipse-workspace/openapi-generator-maven-plugin-template-customization-example/target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/service/GreetApi.java\n    [INFO] Skipping generation of supporting files.\n    ################################################################################\n    # Thanks for using OpenAPI Generator.                                          #\n    # Please consider donation to help us maintain this project 🙏                 #\n    # https://opencollective.com/openapi_generator/donate                          #\n    ################################################################################\n    [INFO] ------------------------------------------------------------------------\n    [INFO] BUILD SUCCESS\n    [INFO] ------------------------------------------------------------------------\n    [INFO] Total time:  0.679 s\n    [INFO] Finished at: 2022-04-07T11:54:52-05:00\n    [INFO] ------------------------------------------------------------------------\n\nThe OpenAPI Generator Maven Plugin generates some useful diagnostic\nand progress output. In particular, it logs the files it generates,\nwhich is a good starting point to confirm you're generating the code\nyou want.\n\nOf course, you can generate a lot more output with `mvn -X -e\ngenerate-sources`.\n\n### Checking the code\n\nLet's see what code we generated:\n\n    $ find target/generated-sources/openapi -type f                                      \n    target/generated-sources/openapi/.openapi-generator/openapi.yml-generate.sha256\n    target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Greeting.java\n    target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Name.java\n    target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/service/GreetApi.java\n\n    $ cat target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Greeting.java\n    package com.sigpwned.greeting.spi.model;\n    \n    import javax.validation.constraints.*;\n    import javax.validation.Valid;\n    \n    import java.util.Objects;\n    import com.fasterxml.jackson.annotation.JsonProperty;\n    import com.fasterxml.jackson.annotation.JsonCreator;\n    import com.fasterxml.jackson.annotation.JsonValue;\n    import com.fasterxml.jackson.annotation.JsonTypeName;\n    \n    \n    \n    @JsonTypeName(\"Greeting\")\n    @javax.annotation.Generated(value = \"org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen\", date = \"2022-04-07T11:54:52.045724-05:00[America/Chicago]\")public class Greeting   {\n      \n      private @Valid String greeting;\n    \n      /**\n       **/\n      public Greeting greeting(String greeting) {\n        this.greeting = greeting;\n        return this;\n      }\n    \n      \n    \n      \n      @JsonProperty(\"greeting\")\n      public String getGreeting() {\n        return greeting;\n      }\n    \n      @JsonProperty(\"greeting\")\n      public void setGreeting(String greeting) {\n        this.greeting = greeting;\n      }\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        Greeting greeting = (Greeting) o;\n        return Objects.equals(this.greeting, greeting.greeting);\n      }\n    \n      @Override\n      public int hashCode() {\n        return Objects.hash(greeting);\n      }\n    \n      @Override\n      public String toString() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"class Greeting {\\n\");\n        \n        sb.append(\"    greeting: \").append(toIndentedString(greeting)).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    }\n\n    $ cat target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/model/Name.java\n    package com.sigpwned.greeting.spi.model;\n    \n    import javax.validation.constraints.*;\n    import javax.validation.Valid;\n    \n    import java.util.Objects;\n    import com.fasterxml.jackson.annotation.JsonProperty;\n    import com.fasterxml.jackson.annotation.JsonCreator;\n    import com.fasterxml.jackson.annotation.JsonValue;\n    import com.fasterxml.jackson.annotation.JsonTypeName;\n    \n    \n    \n    @JsonTypeName(\"Name\")\n    @javax.annotation.Generated(value = \"org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen\", date = \"2022-04-07T11:54:52.045724-05:00[America/Chicago]\")public class Name   {\n      \n      private @Valid String name;\n    \n      /**\n       **/\n      public Name name(String name) {\n        this.name = name;\n        return this;\n      }\n    \n      \n    \n      \n      @JsonProperty(\"name\")\n      public String getName() {\n        return name;\n      }\n    \n      @JsonProperty(\"name\")\n      public void setName(String name) {\n        this.name = name;\n      }\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        Name name = (Name) o;\n        return Objects.equals(this.name, name.name);\n      }\n    \n      @Override\n      public int hashCode() {\n        return Objects.hash(name);\n      }\n    \n      @Override\n      public String toString() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"class Name {\\n\");\n        \n        sb.append(\"    name: \").append(toIndentedString(name)).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    }\n\n    $ cat target/generated-sources/openapi/src/gen/java/com/sigpwned/greeting/spi/service/GreetApi.java\n    package com.sigpwned.greeting.spi.service;\n    \n    import com.sigpwned.greeting.spi.model.Greeting;\n    import com.sigpwned.greeting.spi.model.Name;\n    \n    import javax.ws.rs.*;\n    import javax.ws.rs.core.Response;\n    \n    import java.util.concurrent.CompletionStage;\n    import java.util.concurrent.CompletableFuture;\n    \n    import java.io.InputStream;\n    import java.util.Map;\n    import java.util.List;\n    import javax.validation.constraints.*;\n    import javax.validation.Valid;\n    \n    @Path(\"/greet\")\n    @javax.annotation.Generated(value = \"org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen\", date = \"2022-04-07T11:54:52.045724-05:00[America/Chicago]\")public interface GreetApi {\n    \n        @POST\n        @Consumes({ \"application/json\" })\n        @Produces({ \"application/json\" })\n        Greeting greet(@Valid Name name);\n    \n    }\n\n### Generated Model Classes\n\nWe have two Model classes -- `Greeting` and `Name` -- which correspond\nto our named model objects from the spec. They are totally generic\nPOJOs. They support a default constructor, private attributes with\naccessors, plus sane implementations of `hashCode`, `equals`, and\n`toString`.\n\nThe generator does exactly what it says on the tin here, and we aren't\ncustomizing the Model objects in this example, so we won't spend any\nmore time looking at the Model objects here.\n\n### Generated API Classes\n\nThe API class is more interesting.\n\nWe have one endpoint, `POST /greet`, so we have one method across all\ngenerated API classes. That method is called `greet`, which is its\n`operationId` in the spec.\n\nMethods are collected into classes by the first slug on their endpoint\npaths, so in this case we have one class called `GreetApi`. If our\nspec had two endpoints `POST /greet/one` and `GET /greet/two`, then\nthere would be one class `GreetApi` with two methods in it. If our\nspec had two endpoints `POST /greet` and `POST /farewell`, then there\nwould be two classes `GreetApi` and `FarewellApi` with one method each.\n\nBecause of the configuration values we chose in our POM file:\n\n* `GreetApi` is an interface because `\u003cinterfaceOnly\u003etrue\u003c/interfaceOnly\u003e`\n* `greet` returns a `Greeting` (as opposed to a `Response`) because `\u003creturnResponse\u003efalse\u003c/returnResponse\u003e`\n* `greet` returns a `Greeting` (as opposed to a `CompletionStage\u003cGreeting\u003e`) because `\u003csupportAsync\u003efalse\u003c/supportAsync\u003e`\n\n## Template Customization\n\nNow that we understand the generator's default behavior, let's make\nsome customizations. For the purposes of this example, we will add\nJAX-RS `AsyncResponse` code generation to the generator.\n\n### The Customization Requirements\n\nOut of the box, the OpenAPI Generator supports code generation for\nasynchronous processing using the JAX-RS 2.1 `CompletionStage`\nparadigm. However, some web frameworks -- e.g.,\n[Dropwizard](https://www.dropwizard.io/) -- only support the earlier\n`AsyncResponse` paradigm. Let's add `@Suspended AsyncResponse`\nasynchronous processing code generation to the generator.\n\nIn our example spec, the generator can already generate method\ndefinitions using `CompletionStage` using\n`\u003csupportAsync\u003etrue\u003c/supportAsync\u003e`:\n\n    @POST\n    @Consumes({ \"application/json\" })\n    @Produces({ \"application/json\" })\n    CompletionStage\u003cGreeting\u003e greet(@Valid Name name);\n\nWe want to be able to generate method definitions like this:\n\n    @POST\n    @Consumes({ \"application/json\" })\n    @Produces({ \"application/json\" })\n    void greet(@Valid Name name, @Suspended AsyncResponse response);\n\n### OpenAPI Spec Extensions\n\nThe [OpenAPI Spec](https://swagger.io/specification/) explicitly\nprovides the\n[extensions](https://swagger.io/docs/specification/openapi-extensions/)\nmechanism to allow users to extend the spec for their own\nneeds. Essentially, users can add specially-named attributes anywhere\nin their specs, and they will be treated as \"vendor extensions.\"\n\nFor this example, we will add a new method-level vendor extension,\n`x-async-enabled`, to indicate which methods should use async\nprocessing.\n\nThis will allow us to be use compatible asynchronous processing with\nmore web frameworks, and to declare on a per-method basis exactly\nwhich methods should and should not use asynchronous processing.\n\n### The Template Customizations\n\nThe OpenAPI Generator provides rich customization hooks. The easiest\nto use are\n[template](https://github.com/OpenAPITools/openapi-generator/blob/master/docs/templating.md)\noverrides.\n\nEach generator is implemented using [mustache\ntemplates](https://en.wikipedia.org/wiki/Mustache_%28template_system%29),\nspecifically\n[jmustache](https://github.com/samskivert/jmustache). Typically,\ngenerators implement multiple templates that call each other. The\ngenerator allows users to\n[BYOT](https://openapi-generator.tech/docs/templating/) (\"bring your\nown templates\") to override default templates.\n\nIn our POM, we told the generator we were going to BYOT:\n\n    \u003c!-- We are showing off template customization today. The custom templates are stored here. --\u003e\n    \u003ctemplateDirectory\u003e${project.basedir}/openapi/templates\u003c/templateDirectory\u003e\n\nHere are the templates we brought:\n\n    $ ls openapi/templates\n    api.mustache\n    apiInterface.mustache\n    apiMethod.mustache\n    asyncParams.mustache\n    returnAsyncTypeInterface.mustache\n    returnTypeInterface.mustache\n\nNotice that these templates overlap with [the existing JavaJaxRS/spec\ntemplates](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/JavaJaxRS/spec). When\nthe generator generates code, it will find our templates instead of\nthe default templates, and use them instead. So in essence, each\ntemplate is a customization hook that we can plug into to generate\nwhatever code we need.\n\n### Understanding the Template Customization\n\nThe default `JavaJaxRS/spec` `apiInterface.mustache` template looks like this:\n\n    @{{httpMethod}}{{#subresourceOperation}}\n    @Path(\"{{{path}}}\"){{/subresourceOperation}}{{#hasConsumes}}\n    @Consumes({ {{#consumes}}\"{{{mediaType}}}\"{{^-last}}, {{/-last}}{{/consumes}} }){{/hasConsumes}}{{#hasProduces}}\n    @Produces({ {{#produces}}\"{{{mediaType}}}\"{{^-last}}, {{/-last}}{{/produces}} }){{/hasProduces}}{{#useSwaggerAnnotations}}\n    @ApiOperation(value = \"{{{summary}}}\", notes = \"{{{notes}}}\"{{#hasAuthMethods}}, authorizations = {\n        {{#authMethods}}{{#isOAuth}}@Authorization(value = \"{{name}}\", scopes = {\n            {{#scopes}}@AuthorizationScope(scope = \"{{scope}}\", description = \"{{description}}\"){{^-last}},\n            {{/-last}}{{/scopes}} }){{^-last}},{{/-last}}{{/isOAuth}}\n        {{^isOAuth}}@Authorization(value = \"{{name}}\"){{^-last}},{{/-last}}\n        {{/isOAuth}}{{/authMethods}} }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}\"{{tag}}\"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} })\n    {{#implicitHeadersParams.0}}\n    @io.swagger.annotations.ApiImplicitParams({\n        {{#implicitHeadersParams}}\n        @io.swagger.annotations.ApiImplicitParam(name = \"{{{baseName}}}\", value = \"{{{description}}}\", {{#required}}required = true,{{/required}} dataType = \"{{{dataType}}}\", paramType = \"header\"){{^-last}},{{/-last}}\n        {{/implicitHeadersParams}}\n    })\n    {{/implicitHeadersParams.0}}\n    @ApiResponses(value = { {{#responses}}\n        @ApiResponse(code = {{{code}}}, message = \"{{{message}}}\", response = {{{baseType}}}.class{{#returnContainer}}, responseContainer = \"{{{.}}}\"{{/returnContainer}}){{^-last}},{{/-last}}{{/responses}} }){{/useSwaggerAnnotations}}\n    {{#supportAsync}}{{\u003ereturnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{\u003ereturnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{\u003equeryParams}}{{\u003epathParams}}{{\u003eheaderParams}}{{\u003ebodyParams}}{{\u003eformParams}}{{^-last}},{{/-last}}{{/allParams}});\n\nOur customized template looks like this:\n\n    @{{httpMethod}}{{#subresourceOperation}}\n    @Path(\"{{{path}}}\"){{/subresourceOperation}}{{#hasConsumes}}\n    @Consumes({ {{#consumes}}\"{{{mediaType}}}\"{{^-last}}, {{/-last}}{{/consumes}} }){{/hasConsumes}}{{#hasProduces}}\n    @Produces({ {{#produces}}\"{{{mediaType}}}\"{{^-last}}, {{/-last}}{{/produces}} }){{/hasProduces}}{{#useSwaggerAnnotations}}\n    @ApiOperation(value = \"{{{summary}}}\", notes = \"{{{notes}}}\"{{#hasAuthMethods}}, authorizations = {\n        {{#authMethods}}{{#isOAuth}}@Authorization(value = \"{{name}}\", scopes = {\n            {{#scopes}}@AuthorizationScope(scope = \"{{scope}}\", description = \"{{description}}\"){{^-last}},\n            {{/-last}}{{/scopes}} }){{^-last}},{{/-last}}{{/isOAuth}}\n        {{^isOAuth}}@Authorization(value = \"{{name}}\"){{^-last}},{{/-last}}\n        {{/isOAuth}}{{/authMethods}} }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}\"{{tag}}\"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} })\n    {{#implicitHeadersParams.0}}\n    @io.swagger.annotations.ApiImplicitParams({\n        {{#implicitHeadersParams}}\n        @io.swagger.annotations.ApiImplicitParam(name = \"{{{baseName}}}\", value = \"{{{description}}}\", {{#required}}required = true,{{/required}} dataType = \"{{{dataType}}}\", paramType = \"header\"){{^-last}},{{/-last}}\n        {{/implicitHeadersParams}}\n    })\n    {{/implicitHeadersParams.0}}\n    @ApiResponses(value = { {{#responses}}\n        @ApiResponse(code = {{{code}}}, message = \"{{{message}}}\", response = {{{baseType}}}.class{{#returnContainer}}, responseContainer = \"{{{.}}}\"{{/returnContainer}}){{^-last}},{{/-last}}{{/responses}} }){{/useSwaggerAnnotations}}\n    {{#supportAsync}}{{\u003ereturnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{\u003ereturnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{\u003equeryParams}}{{\u003epathParams}}{{\u003eheaderParams}}{{\u003ebodyParams}}{{\u003eformParams}}{{^-last}},{{/-last}}{{/allParams}}{{#vendorExtensions.x-async-enabled}}{{#allParams.0}},{{/allParams.0}}{{\u003easyncParams}}{{/vendorExtensions.x-async-enabled}});\n\nThese templates are pretty dense! Let's look at the differences between the two:\n\n    $ diff original.mustache customization.mustache\n    20c20\n    \u003c     {{#supportAsync}}{{\u003ereturnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{\u003ereturnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{\u003equeryParams}}{{\u003epathParams}}{{\u003eheaderParams}}{{\u003ebodyParams}}{{\u003eformParams}}{{^-last}},{{/-last}}{{/allParams}});\n    ---\n    \u003e     {{#supportAsync}}{{\u003ereturnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{\u003ereturnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{\u003equeryParams}}{{\u003epathParams}}{{\u003eheaderParams}}{{\u003ebodyParams}}{{\u003eformParams}}{{^-last}},{{/-last}}{{/allParams}}{{#vendorExtensions.x-async-enabled}}{{#allParams.0}},{{/allParams.0}}{{\u003easyncParams}}{{/vendorExtensions.x-async-enabled}});\n\nThere's only one line of difference! Essentially, we just added logic\nto generate special async parameters using a new template if the\nvendor extension is present and truthy.\n\nSo even though these templates are pretty complex, the good news is\nthat they're also pretty well-factored and easy to work on once you\nunderstand the task you're trying to accomplish.\n\nOf course, this isn't the only change. Readers are encouraged to look\nat the other changes, too. But they're all pretty simple, like this\none.\n\n### Confirming Code Generation\n\nIf we update our spec to use `x-async-enabled: true` on a method, we\ndo indeed get our updated code generation:\n\n    @POST\n    @Consumes({ \"application/json\" })\n    @Produces({ \"application/json\" })\n    void greet(@Valid Name name,@Suspended final AsyncResponse asyncResponse);\n\nSuccess!\n\n## Summary\n\nThat's it! Once you have a working code generation configuration, you\ntypically only need to override a couple of template updates to effect\nany code generation change you want. Good luck, and happy customizing!\n\nThis configuration should work and generate useful code for generating\n`AsyncResponse` methods using the OpenAPI Generator for anyone who\nneeds it.\n\n## References\n\n[This Stack Overflow\nanswer](https://stackoverflow.com/a/57791107/2103602) was the starting\npoint for this example. Thank you,\n[philonous](https://stackoverflow.com/users/191466/philonous)!\n\nThe JAX-RS 2.1 spec is available\n[here](https://download.oracle.com/otndocs/jcp/jaxrs-2_1-final-eval-spec/index.html)\nfor anyone who is curious to read it. The `AsyncResponse` asynchronous\nprocessing paradigm is covered in section 8.2.1. The `CompletionStage` asynchronous\nprocessing paradigm is covered in section 8.2.2.","funding_links":["https://opencollective.com/openapi_generator/donate"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsigpwned%2Fopenapi-generator-maven-plugin-template-customization-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsigpwned%2Fopenapi-generator-maven-plugin-template-customization-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsigpwned%2Fopenapi-generator-maven-plugin-template-customization-example/lists"}