Skip to content

Spring Boot With OpenApi Advances#

Add Custom Annotations Into Models#

  • From last section Spring Boot With OpenApi, we know how to set up and use basic features of OpenApi Generator for our Spring Boot project.
  • However, in some special cases, we will need do more than that like adding some custom annotations into specific generated classes or only on just some fields in them.
  • OpenApi supports us many way to achieve this one like using configuration in pom.xml, SUPPORTED VENDOR EXTENSIONS and  specification extensions or  vendor extensions. Base on the business requirement then we can choose the most suitable one.

  • Below are SUPPORTED VENDOR EXTENSIONS that the OpenApi had supported.

Extension name Description Applicable for Default value
x-discriminator-value Used with model inheritance to specify value for discriminator that identifies current model MODEL
x-implements Ability to specify interfaces that model must implements MODEL empty array
x-setter-extra-annotation Custom annotation that can be specified over java setter for specific field FIELD When field is array & uniqueItems, then this extension is used to add @JsonDeserialize(as = LinkedHashSet.class) over setter, otherwise no value
x-tags Specify multiple swagger tags for operation OPERATION null
x-accepts Specify custom value for 'Accept' header for operation OPERATION null
x-content-type Specify custom value for 'Content-Type' header for operation OPERATION null
x-class-extra-annotation List of custom annotations to be added to model MODEL null
x-field-extra-annotation List of custom annotations to be added to property FIELD null
x-spring-paginated Add org.springframework.data.domain.Pageable to controller method. Can be used to handle page & size query parameters OPERATION false
  • So While the OpenAPI specification tries to accommodate most use cases, additional data can be used to extend the specification and augment its functionality. These additional properties are known as specification extensions (previously called "vendor extensions"). The extension properties are implemented as patterned fields that start with the prefix x- naming convention such as x-custom-annotation. They can be used to describe extra functionality that is not covered by the standard OpenAPI Specification.

  • The specification extensions are grouped according to the element(s) of the OpenAPI specification that they extend. Specification extensions can only be used in the sections of your OpenAPI definition that support them - for example, you can't use an extension that modifies the info object inside the tag object.

  • Now, let's go through examples below then we can understand easier.

Using ConfigOptions In Pom.xml#

  • Usually, to protect our customer data from incoming requests which can contain invalid data or injection script, we usually create model classes for which contains validation like the format of field, the min length, the max length...etc.
  • So the OpenApi had supported this validation feature by enable useBeanValidationas true in the configOptions in pom.xml. See example below.

Note: By default the useBeanValidation is set as true by OpenApi. So we can skip setting it again in the configOptions.

pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<build>
        <plugins>
                <plugin>
                        <groupId>org.openapitools</groupId>
                        <artifactId>openapi-generator-maven-plugin</artifactId>
                        <version>6.0.0</version>
                        <executions>
                                <execution>
                                        <goals>
                                                <goal>generate</goal>
                                        </goals>
                                        <configuration>
                                                <!-- path to the openapi file spec `.yml` -->
                                                <inputSpec>
                                                        ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                                                </inputSpec>
                                                <generatorName>spring</generatorName>
                                                <!-- generated package for api interface -->
                                                <apiPackage>com.springboot.cloud.openfeign.openapi.server.app.api</apiPackage>
                                                <!-- generated package for models -->
                                                <modelPackage>com.springboot.cloud.openfeign.openapi.server.app.model</modelPackage>
                                                <!-- using supportingFilesToGenerate -->
                                                <supportingFilesToGenerate>
                                                        ApiUtil.java
                                                </supportingFilesToGenerate>
                                                <configOptions>
                                                        <delegatePattern>false</delegatePattern>
                                                        <dateLibrary>java8</dateLibrary>
                                                        <interfaceOnly>true</interfaceOnly>
                                                        <useBeanValidation>true</useBeanValidation>
                                                        <openApiNullable>true</openApiNullable>
                                                </configOptions>
                                        </configuration>
                                </execution>
                        </executions>
                </plugin>
        </plugins>
</build>
  • Now, let create the openapi-server.yml and add some validation as Min length, Max length as below.
openapi-server.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
openapi: 3.0.3
info:
  title: Swagger Openapi Server
  description: 'This is the Openapi Specification For Feign Server'
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
  - name: server
    description: all server apis

paths:
  /v1/server/customers:
    post:
      tags:
        - server
      summary: Create a customer
      description: create a customer
      operationId: createCustomer
      requestBody:
        description: request body
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerRequest'
        required: true
      responses:
        201:
          description: successful operation
          content:
            application/json:
              schema:
                type: string
                format: uuid
    get:
      tags:
        - server
      summary: get customers
      description: get customers
      operationId: getCustomers
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
components:
  schemas:
    CustomerRequest:
      type: object
      required:
        - email
      properties:
        fullName:
          type: string
          example: Nguyen Minh Duc
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
          example: ducnguyen@gmail.com
        address:
          type: string
          example: 3/115 Binh Duong
        phone:
          type: string
          example: 0999123445
        gender:
          type: string
          enum: [M, F]
        dob:
          type: string
          format: date
    Customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        fullName:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
        phone:
          type: string
        gender:
          type: string
          enum: [ M, F ]
        dob:
          type: string
          format: date
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
  • Then let's build our source code and go to generated source, then you can see our fields have been added validation as below.
CustomerRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
....

/**  
 * Get fullName * @return fullName  
*/  
@Size(min = 1, max = 100)   
@Schema(name = "fullName", example = "Nguyen Minh Duc", required = false)  
public String getFullName() {  
  return fullName;  
}  

public void setFullName(String fullName) {  
  this.fullName = fullName;  
}

....
  • Now, in case you want to add some custom annotations of lombok into your model classes, you can use the configOptions with properties additionalModelTypeAnnotations. In which In which you should define full path of your annotations. Ex: @lombok.Builder. See example below.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<build>
            <plugins>
                    <plugin>
                            <groupId>org.openapitools</groupId>
                            <artifactId>openapi-generator-maven-plugin</artifactId>
                            <version>6.0.0</version>
                            <executions>
                                    <execution>
                                            <goals>
                                                    <goal>generate</goal>
                                            </goals>
                                            <configuration>
                                                    <!-- path to the openapi file spec `.yml` -->
                                                    <inputSpec>
                                                            ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                                                    </inputSpec>
                                                    <generatorName>spring</generatorName>
                                                    <!-- generated package for api interface -->
                                                    <apiPackage>com.springboot.cloud.openfeign.openapi.server.app.api</apiPackage>
                                                    <!-- generated package for models -->
                                                    <modelPackage>com.springboot.cloud.openfeign.openapi.server.app.model</modelPackage>
                                                    <!-- using supportingFilesToGenerate -->
                                                    <supportingFilesToGenerate>
                                                            ApiUtil.java
                                                    </supportingFilesToGenerate>
                                                    <configOptions>
                                                            <delegatePattern>false</delegatePattern>
                                                            <dateLibrary>java8</dateLibrary>
                                                            <interfaceOnly>true</interfaceOnly>
                                                            <useBeanValidation>true</useBeanValidation>
                                                            <openApiNullable>true</openApiNullable>
                                                            <additionalModelTypeAnnotations>
                                                                    @lombok.Builder
                                                                    @lombok.NoArgsConstructor
                                                                    @lombok.AllArgsConstructor
                                                            </additionalModelTypeAnnotations>
                                                    </configOptions>
                                            </configuration>
                                    </execution>
                            </executions>
                    </plugin>
            </plugins>
    </build>
CustomerRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
....

/**
 * CustomerRequest
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-14T19:29:25.091+07:00[Asia/Ho_Chi_Minh]")
public class CustomerRequest {

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("email")
  private String email;

  @JsonProperty("address")
  private String address;

....

}
Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
....

/**
 * Customer
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-14T19:29:25.091+07:00[Asia/Ho_Chi_Minh]")
public class Customer {

  @JsonProperty("id")
  private UUID id;

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("email")
  private String email;

....

}

Note: when you use additionalModelTypeAnnotations in configOption so it will be applied to all your generated models.

Using Supported Vendor Extensions#

  • Now, let's assume that you have some special annotations and you just want to apply them on some special models not on all models. In this case, you may want to use the supported vendor extesions of OpenApi.
  • For example we will add the annotation @lombok.Setter for only CustomerRequest model class and annotation @javax.validation.constraints.PastOrPresent for only field dob in Customer model class.
  • Let's use x-class-extra-annotation to add annotation for model CustomerRequest and the x-field-extra-annotation to add annotation for special field of Customer model in openapi-server.yml` as below.
openapi-server.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
openapi: 3.0.3
info:
  title: Swagger Openapi Server
  description: 'This is the Openapi Specification For Feign Server'
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
  - name: server
    description: all server apis

paths:
  /v1/server/customers:
    post:
      tags:
        - server
      summary: Create a customer
      description: create a customer
      operationId: createCustomer
      requestBody:
        description: request body
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerRequest'
        required: true
      responses:
        201:
          description: successful operation
          content:
            application/json:
              schema:
                type: string
                format: uuid
    get:
      tags:
        - server
      summary: get customers
      description: get customers
      operationId: getCustomers
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
components:
  schemas:
    CustomerRequest:
      type: object
      x-class-extra-annotation: "@lombok.Setter"
      required:
        - email
      properties:
        fullName:
          type: string
          example: Nguyen Minh Duc
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
          example: ducnguyen@gmail.com
        address:
          type: string
          example: 3/115 Binh Duong
        phone:
          type: string
          example: 0999123445
        gender:
          type: string
          enum: [M, F]
        dob:
          type: string
          format: date
    Customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        fullName:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
        phone:
          type: string
        gender:
          type: string
          enum: [ M, F ]
        dob:
          type: string
          format: date
          x-field-extra-annotation: "@javax.validation.constraints.PastOrPresent"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
  • Now, let's build our source code and go to generated source.
  • Then you can see in the CustomerRequest model you will see there is annotation @lombok.Setter above the class. But you will not see it on Customer model class.
CustomerRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
....

/**
 * CustomerRequest
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-15T09:46:47.456+07:00[Asia/Ho_Chi_Minh]")
@lombok.Setter
public class CustomerRequest {

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("email")
  private String email;

  @JsonProperty("address")
  private String address;

....

}
Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
....

/**
 * Customer
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-15T09:46:47.456+07:00[Asia/Ho_Chi_Minh]")
public class Customer {

  @JsonProperty("id")
  private UUID id;

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("email")
  private String email;

....

}
  • Then let's continue to check the Customer model class, you will see the special annotation PastOrPresent for field dob as below. But you will not see it in the CustomerRequest model class.
Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
....

@JsonProperty("gender")
private GenderEnum gender;

@JsonProperty("dob")
@javax.validation.constraints.PastOrPresent
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate dob;

@JsonProperty("createdAt")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime createdAt;

....
CustomerRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
....

@JsonProperty("gender")
private GenderEnum gender;

@JsonProperty("dob")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate dob;

public CustomerRequest fullName(String fullName) {
    this.fullName = fullName;
    return this;
}

....

Using Vendor Extensions#

  • As you can see in the example above, we can add specific annotations for classes and fields that we want. However, every time we add an annotation we have to put full path of it and what happens if we have to add multiple annotations on a specific field or a specific class?
  • If we use the Supported Vendor Extension above, we can do it in the openapi-server.yml like below.
openapi-server.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
openapi: 3.0.3
info:
  title: Swagger Openapi Server
  description: 'This is the Openapi Specification For Feign Server'
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
  - name: server
    description: all server apis

paths:
  /v1/server/customers:
    post:
      tags:
        - server
      summary: Create a customer
      description: create a customer
      operationId: createCustomer
      requestBody:
        description: request body
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerRequest'
        required: true
      responses:
        201:
          description: successful operation
          content:
            application/json:
              schema:
                type: string
                format: uuid
    get:
      tags:
        - server
      summary: get customers
      description: get customers
      operationId: getCustomers
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
components:
  schemas:
    CustomerRequest:
      type: object
      x-class-extra-annotation: "@lombok.Setter/n@lombok.Getter"
      required:
        - email
      properties:
        fullName:
          type: string
          example: Nguyen Minh Duc
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
          example: ducnguyen@gmail.com
        address:
          type: string
          example: 3/115 Binh Duong
        phone:
          type: string
          example: 0999123445
        gender:
          type: string
          enum: [M, F]
        dob:
          type: string
          format: date
    Customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        fullName:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
        phone:
          type: string
        gender:
          type: string
          enum: [ M, F ]
        dob:
          type: string
          format: date
          x-field-extra-annotation: "@javax.validation.constraints.PastOrPresent/n@javax.validation.constraints.Size(min = 3, max=20)"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
  • As you can see, we have to put full path of annotation and for multiple annotation we have to put space or "/n". It is bad for developer to write and for people who read this OpenApi specification. Moreover, when the models are generated, so also see annotation with full path, so it is not nice, we would like to see the imports on the top of the generated class and only annotations on classes or fields.
  • So, using Vendor Extension will help us to solve this issue.
  • Because in this example, we use annotations of javax validator so to make sure this library is not imported by default and we are controlling imports for annotations so we will disable useBeanValidation and we also need to add templateDirectory for using custom OpenApi templates.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  <build>
        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>6.0.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <!-- path to the openapi file spec `.yml` -->
                            <inputSpec>
                                ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                            </inputSpec>
                            <generatorName>spring</generatorName>
                            <!-- generated package for api interface -->
                            <apiPackage>com.springboot.cloud.openfeign.openapi.server.app.api</apiPackage>
                            <!-- generated package for models -->
                            <modelPackage>com.springboot.cloud.openfeign.openapi.server.app.model</modelPackage>
                            <!-- using supportingFilesToGenerate -->
                            <supportingFilesToGenerate>
                                ApiUtil.java
                            </supportingFilesToGenerate>
                            <!-- using templateDirectory custom templates for openapi generator -->                            <templateDirectory>${project.basedir}/src/main/resources/templates/java-spring</templateDirectory>
                            <configOptions>
                                <delegatePattern>false</delegatePattern>
                                <dateLibrary>java8</dateLibrary>
                                <interfaceOnly>true</interfaceOnly>
                                <useBeanValidation>false</useBeanValidation>
                                <additionalModelTypeAnnotations>
                                    @lombok.Builder
                                    @lombok.NoArgsConstructor
                                    @lombok.AllArgsConstructor
                                </additionalModelTypeAnnotations>
                                <openApiNullable>false</openApiNullable>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  • Then, we can go to this github repo to download full templates of OpenApi generator of JavaSpring and put it into our resources folder.

 #zoom

  • Then we can create a custom vendor extension x-internal-imports for list of imports library by adding the mustache script below into the model.mustache.
1
2
3
{{#vendorExtensions.x-internal-imports}}
{{{.}}}
{{/vendorExtensions.x-internal-imports}}

model.mustache
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package {{package}};

import java.net.URI;
import java.util.Objects;
{{#imports}}import {{import}};
{{/imports}}
{{#openApiNullable}}
import org.openapitools.jackson.nullable.JsonNullable;
{{/openApiNullable}}
{{#serializableModel}}
import java.io.Serializable;
{{/serializableModel}}
import java.time.OffsetDateTime;
{{#useBeanValidation}}
{{#useJakartaEe}}
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
{{/useJakartaEe}}
{{^useJakartaEe}}
import javax.validation.Valid;
import javax.validation.constraints.*;
{{/useJakartaEe}}
{{/useBeanValidation}}
{{#performBeanValidation}}
import org.hibernate.validator.constraints.*;
{{/performBeanValidation}}
{{#jackson}}
{{#withXml}}
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
{{/withXml}}
{{/jackson}}
{{#swagger2AnnotationLibrary}}
import io.swagger.v3.oas.annotations.media.Schema;
{{/swagger2AnnotationLibrary}}

{{#withXml}}
import javax.xml.bind.annotation.*;
{{/withXml}}
{{^parent}}
{{#hateoas}}
import org.springframework.hateoas.RepresentationModel;
{{/hateoas}}
{{/parent}}

import java.util.*;
{{#useJakartaEe}}
import jakarta.annotation.Generated;
{{/useJakartaEe}}
{{^useJakartaEe}}
import javax.annotation.Generated;
{{/useJakartaEe}}

{{#models}}
{{#model}}

{{#vendorExtensions.x-internal-imports}}
{{{.}}}
{{/vendorExtensions.x-internal-imports}}

{{#isEnum}}
{{>enumOuterClass}}
{{/isEnum}}
{{^isEnum}}
{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}
{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}
{{/isEnum}}
{{/model}}
{{/models}}
- Then for using list of annotations on specific classes and fields, we can go to pojo.mustache file and edit the {{{vendorExtensions.x-class-extra-annotation}}} and {{{vendorExtensions.x-field-extra-annotation}}}to {{{.}}} as below.

pojo.mustache
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/**
 * {{description}}{{^description}}{{classname}}{{/description}}
 */
{{>additionalModelTypeAnnotations}}
{{#description}}
{{#swagger1AnnotationLibrary}}
@ApiModel(description = "{{{description}}}")
{{/swagger1AnnotationLibrary}}
{{#swagger2AnnotationLibrary}}
@Schema({{#name}}name = "{{name}}", {{/name}}description = "{{{description}}}")
{{/swagger2AnnotationLibrary}}
{{/description}}
{{#discriminator}}
{{>typeInfoAnnotation}}
{{/discriminator}}
{{#jackson}}
{{#isClassnameSanitized}}
@JsonTypeName("{{name}}")
{{/isClassnameSanitized}}
{{/jackson}}
{{#withXml}}
{{>xmlAnnotation}}
{{/withXml}}
{{>generatedAnnotation}}
{{#vendorExtensions.x-class-extra-annotation}}
{{{.}}}
{{/vendorExtensions.x-class-extra-annotation}}
public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#serializableModel}}

  private static final long serialVersionUID = 1L;
{{/serializableModel}}
  {{#vars}}

    {{#isEnum}}
    {{^isContainer}}
{{>enumClass}}
    {{/isContainer}}
    {{#isContainer}}
    {{#mostInnerItems}}
{{>enumClass}}
    {{/mostInnerItems}}
    {{/isContainer}}
    {{/isEnum}}
  {{#jackson}}
  @JsonProperty("{{baseName}}")
  {{#withXml}}
  @JacksonXmlProperty({{#isXmlAttribute}}isAttribute = true, {{/isXmlAttribute}}{{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}")
  {{/withXml}}
  {{/jackson}}
  {{#gson}}
  @SerializedName("{{baseName}}")
  {{/gson}}
  {{#vendorExtensions.x-field-extra-annotation}}
  {{{.}}}
  {{/vendorExtensions.x-field-extra-annotation}}
  {{#isContainer}}
  {{#useBeanValidation}}@Valid{{/useBeanValidation}}
  {{#openApiNullable}}
  private {{>nullableDataType}} {{name}} = {{#isNullable}}JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}}{{/isNullable}};
  {{/openApiNullable}}
  {{^openApiNullable}}
  private {{>nullableDataType}} {{name}} = {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}};
  {{/openApiNullable}}
  {{/isContainer}}
  {{^isContainer}}
  {{#isDate}}
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  {{/isDate}}
  {{#isDateTime}}
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
  {{/isDateTime}}
  {{#openApiNullable}}
  private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
  {{/openApiNullable}}
  {{^openApiNullable}}
  private {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
  {{/openApiNullable}}
  {{/isContainer}}
  {{/vars}}
  {{#vars}}

  {{! begin feature: fluent setter methods }}
  public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
    {{#openApiNullable}}
    this.{{name}} = {{#isNullable}}JsonNullable.of({{name}}){{/isNullable}}{{^isNullable}}{{name}}{{/isNullable}};
    {{/openApiNullable}}
    {{^openApiNullable}}
    this.{{name}} = {{name}};
    {{/openApiNullable}}
    return this;
  }
  {{#isArray}}

  public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) {
    {{#openApiNullable}}
    {{^required}}
    if (this.{{name}} == null{{#isNullable}} || !this.{{name}}.isPresent(){{/isNullable}}) {
      this.{{name}} = {{#isNullable}}JsonNullable.of({{{defaultValue}}}){{/isNullable}}{{^isNullable}}{{{defaultValue}}}{{/isNullable}};
    }
    {{/required}}
    this.{{name}}{{#isNullable}}.get(){{/isNullable}}.add({{name}}Item);
    {{/openApiNullable}}
    {{^openApiNullable}}
    if (this.{{name}} == null) {
      this.{{name}} = {{{defaultValue}}};
    }
    this.{{name}}.add({{name}}Item);
    {{/openApiNullable}}
    return this;
  }
  {{/isArray}}
  {{#isMap}}

  public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
    {{^required}}
    if (this.{{name}} == null) {
      this.{{name}} = {{{defaultValue}}};
    }
    {{/required}}
    this.{{name}}.put(key, {{name}}Item);
    return this;
  }
  {{/isMap}}
  {{! end feature: fluent setter methods }}
  {{! begin feature: getter and setter }}

  /**
  {{#description}}
   * {{{.}}}
  {{/description}}
  {{^description}}
   * Get {{name}}
  {{/description}}
  {{#minimum}}
   * minimum: {{.}}
  {{/minimum}}
  {{#maximum}}
   * maximum: {{.}}
  {{/maximum}}
   * @return {{name}}
  */
  {{#vendorExtensions.x-extra-annotation}}
  {{{vendorExtensions.x-extra-annotation}}}
  {{/vendorExtensions.x-extra-annotation}}
  {{#useBeanValidation}}
  {{>beanValidation}}
  {{/useBeanValidation}}
  {{^useBeanValidation}}
  {{#required}}@NotNull{{/required}}
  {{/useBeanValidation}}
  {{#swagger2AnnotationLibrary}}
  @Schema(name = "{{{baseName}}}", {{#isReadOnly}}accessMode = Schema.AccessMode.READ_ONLY, {{/isReadOnly}}{{#example}}example = "{{{.}}}", {{/example}}{{#description}}description = "{{{.}}}", {{/description}}required = {{{required}}})
  {{/swagger2AnnotationLibrary}}
  {{#swagger1AnnotationLibrary}}
  @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}")
  {{/swagger1AnnotationLibrary}}
  public {{>nullableDataType}} {{getter}}() {
    return {{name}};
  }

  {{#vendorExtensions.x-setter-extra-annotation}}
  {{{vendorExtensions.x-setter-extra-annotation}}}
  {{/vendorExtensions.x-setter-extra-annotation}}
  public void {{setter}}({{>nullableDataType}} {{name}}) {
    this.{{name}} = {{name}};
  }
  {{! end feature: getter and setter }}
  {{/vars}}
  {{#parentVars}}

  {{! begin feature: fluent setter methods for inherited properties }}
  public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
    super.{{setter}}({{name}});
    return this;
  }
  {{#isArray}}

  public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) {
    super.add{{nameInCamelCase}}Item({{name}}Item);
    return this;
  }
  {{/isArray}}
  {{#isMap}}

  public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
    super.put{{nameInCamelCase}}Item(key, {{name}}Item);
    return this;
  }
  {{/isMap}}
  {{! end feature: fluent setter methods for inherited properties }}
  {{/parentVars}}

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }{{#hasVars}}
    {{classname}} {{classVarName}} = ({{classname}}) o;
    return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} &&
        {{/-last}}{{/vars}}{{#parent}} &&
        super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
    return true;{{/hasVars}}
  }
  {{#vendorExtensions.x-jackson-optional-nullable-helpers}}

  private static <T> boolean equalsNullable(JsonNullable<T> a, JsonNullable<T> b) {
    return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
  }
  {{/vendorExtensions.x-jackson-optional-nullable-helpers}}

  @Override
  public int hashCode() {
    return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}});
  }
  {{#vendorExtensions.x-jackson-optional-nullable-helpers}}

  private static <T> int hashCodeNullable(JsonNullable<T> a) {
    if (a == null) {
      return 1;
    }
    return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
  }
  {{/vendorExtensions.x-jackson-optional-nullable-helpers}}

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class {{classname}} {\n");
    {{#parent}}
    sb.append("    ").append(toIndentedString(super.toString())).append("\n");
    {{/parent}}
    {{#vars}}sb.append("    {{name}}: ").append(toIndentedString({{name}})).append("\n");
    {{/vars}}sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}
  • Finally, let's go to OpenApi specification openapi-server.yml and you can use custom vendor extension x-internal-imports to create a list of import library for your specific model. For x-class-extra-annotation and x-field-extra-annotation you can use single or list of annotations.
openapi-server.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
openapi: 3.0.3
info:
  title: Swagger Openapi Server
  description: 'This is the Openapi Specification For Feign Server'
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
- name: server
  description: all server apis

paths:
  /v1/server/customers:
    post:
      tags:
      - server
      summary: Create a customer
      description: create a customer
      operationId: createCustomer
      requestBody:
        description: request body
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerRequest'
        required: true
      responses:
        201:
          description: successful operation
          content:
            application/json:
              schema:
                type: string
                format: uuid
    get:
      tags:
      - server
      summary: get customers
      description: get customers
      operationId: getCustomers
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
components:
  schemas:
    CustomerRequest:
      type: object
      x-class-extra-annotation:
        - "@Setter"
        - "@Getter"
      x-internal-imports:
        - "import lombok.Setter;"
        - "import lombok.Getter;"
        - "import javax.validation.constraints.Max;"
        - "import javax.validation.constraints.Min;"
        - "import javax.validation.constraints.Size;"
      properties:
        fullName:
          type: string
          example: Nguyen Minh Duc
        age:
          type: integer
          format: int32
          x-field-extra-annotation:
            - "@Min(value = 18)"
            - "@Max(value = 200)"
        email:
          type: string
          format: email
          example: ducnguyen@gmail.com
        address:
          type: string
          x-field-extra-annotation: "@Size(min = 4, max = 200)"
          example: 3/115 Binh Duong
        phone:
          type: string
          example: 0999123445
        gender:
          type: string
          enum: [M, F]
        dob:
          type: string
          format: date
    Customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
          minLength: 16
        fullName:
          type: string
          minLength: 1
        email:
          type: string
          format: email
          minLength: 3
        address:
          type: string
        phone:
          type: string
        gender:
          type: string
          enum: [ M, F ]
        dob:
          type: string
          format: date
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
  • Finally, after build our project, we can see the generated CustomerRequest model as below.
CustomerRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.springboot.cloud.openfeign.openapi.server.app.model;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.OffsetDateTime;
import io.swagger.v3.oas.annotations.media.Schema;


import java.util.*;
import javax.annotation.Generated;


import lombok.Setter;
import lombok.Getter;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;


/**
 * CustomerRequest
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-15T11:20:37.988+07:00[Asia/Ho_Chi_Minh]")
@Setter
@Getter
public class CustomerRequest {

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("age")
  @Min(value = 18)
  @Max(value = 200)
  private Integer age;

  @JsonProperty("email")
  private String email;

  @JsonProperty("address")
  @Size(min = 4, max = 200)
  private String address;

....

}
  • So, we had the list of imports, annotations for classes and fields as we defined for the CustomerRequest in the openapi-server.yml and you will not see them in the Customer model.
Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.springboot.cloud.openfeign.openapi.server.app.model;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.UUID;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.OffsetDateTime;
import io.swagger.v3.oas.annotations.media.Schema;


import java.util.*;
import javax.annotation.Generated;

/**
 * Customer
 */
@lombok.Builder
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-15T11:20:37.988+07:00[Asia/Ho_Chi_Minh]")
public class Customer {

  @JsonProperty("id")
  private UUID id;

  @JsonProperty("fullName")
  private String fullName;

  @JsonProperty("email")
  private String email;

  @JsonProperty("address")
  private String address;

  @JsonProperty("phone")
  private String phone;


....

}

See Also#

References#