eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Introduction

OpenAPI Generator is a tool that allows us to quickly generate client and server code from REST API definitions, supporting multiple languages and frameworks. Although most of the time the generated code is ready to be used with no modifications, there may be scenarios in which we need to customize it.

In this tutorial, we’ll learn how to use custom templates to address these scenarios.

2. OpenAPI Generator Project Setup

Before exploring customization, let’s run through a quick overview of a typical usage scenario for this tool: generating server-side code from a given API definition. We assume we already have a base Spring Boot MVC application built with Maven, so we’ll use the appropriate plugin for that:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.7.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/api/quotes.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
                <templateResourcePath>${project.basedir}/src/templates/JavaSpring</templateResourcePath>
                <configOptions>
                    <dateLibrary>java8</dateLibrary>
                    <openApiNullable>false</openApiNullable>
                    <delegatePattern>true</delegatePattern>
                    <apiPackage>com.baeldung.tutorials.openapi.quotes.api</apiPackage>
                    <modelPackage>com.baeldung.tutorials.openapi.quotes.api.model</modelPackage>
                    <documentationProvider>source</documentationProvider>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

With this configuration, the generated code will go into the target/generated-sources/openapi folder. Moreover, our project also needs to add a dependency to the OpenAPI V3 annotation library:

<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>2.2.3</version>
</dependency>

The latest versions of the plugins and this dependency are available on Maven Central:

The API for this tutorial consists of a single GET operation that returns a quote for a given financial instrument symbol:

openapi: 3.0.0
info:
  title: Quotes API
  version: 1.0.0
servers:
  - description: Test server
    url: http://localhost:8080
paths:
  /quotes/{symbol}:
    get:
      tags:
        - quotes
      summary: Get current quote for a security
      operationId: getQuote
      parameters:
        - name: symbol
          in: path
          required: true
          description: Security's symbol
          schema:
            type: string
            pattern: '[A-Z0-9]+'
      responses:
        '200':
            description: OK
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/QuoteResponse'
components:
  schemas:
    QuoteResponse:
      description: Quote response
      type: object
      properties:
        symbol:
          type: string
          description: security's symbol
        price:
          type: number
          description: Quote value
        timestamp:
          type: string
          format: date-time

Even without any written code, the resulting project can already serve API calls thanks to the default implementation of the QuotesApi – although it will always return a 502 error since the method is not implemented.

3. API Implementation

The next step is to code an implementation of the QuotesApiDelegate interface. Since we’re using a delegate pattern, we don’t need to worry about MVC or OpenAPI-specific annotations, as those will be kept apart in the generated controller.

This approach ensures that, if we later decide to add a library like SpringDoc or similar to the project, the annotations upon which those libraries depend will always be in sync with the API definition. Another benefit is that contract modifications will also change the delegate interface, thus making the project unbuildable. This is good, as it minimizes runtime errors that can happen in code-first approaches.

In our case, the implementation consists of a single method that uses a BrokerService to retrieve quotes:

@Component
public class QuotesApiImpl implements QuotesApiDelegate {

    // ... fields and constructor omitted

    @Override
    public ResponseEntity<QuoteResponse> getQuote(String symbol) {
        var price = broker.getSecurityPrice(symbol);
        var quote = new QuoteResponse();
        quote.setSymbol(symbol);
        quote.setPrice(price);
        quote.setTimestamp(OffsetDateTime.now(clock));
        return ResponseEntity.ok(quote);
    }
}

We also inject a Clock to provide the timestamp field required by the returned QuoteResponse. This is a small implementation detail that makes it easier to unit-test code that uses the current time. For instance, we can simulate the behavior of the code under test at a specific point in time using Clock.fixed(). The unit test for the implementation class uses this approach.

Finally, we’ll implement a BrokerService that simply returns a random quote, which is enough for our purposes.

We can verify that this code works as expected by running the integration test:

@Test
void whenGetQuote_thenSuccess() {
    var response = restTemplate.getForEntity("http://localhost:" + port + "/quotes/BAEL", QuoteResponse.class);
    assertThat(response.getStatusCode())
      .isEqualTo(HttpStatus.OK);
}

4. OpenAPI Generator Customization Scenario

So far, we’ve implemented a service with no customization. Let’s consider the following scenario: As an API definition author, I’d like to specify that a given operation may return a cached result. The OpenAPI specification allows this kind of non-standard behavior through a mechanism called vendor extensions, which can be applied to many (but not all) elements.

For our example, we’ll define an x-spring-cacheable extension to be applied on any operation we want to have this behavior. This is the modified version of our initial API with this extension applied:

# ... other definitions omitted
paths:
  /quotes/{symbol}:
    get:
      tags:
        - quotes
      summary: Get current quote for a security
      operationId: getQuote
      x-spring-cacheable: true
      parameters:
# ... more definitions omitted

Now, if we run the generator again with mvn generate-sources, nothing will happen. This is expected because, although still valid, the generator doesn’t know what to do with this extension. More precisely, the templates used by the generator don’t make any use of the extension.

Upon closer examination of the generated code, we see that we can achieve our goal by adding a @Cacheable annotation on the delegate interface methods that match API operations having our extension. Let’s explore how to do this next.

4.1. Customization Options

The OpenAPI Generator tool supports two customization approaches:

  • Adding a new custom generator, created from scratch or by extending an existing one
  • Replacing templates used by an existing generator with a custom one

The first option is more “heavy-weight” but allows full control of the artifacts generated. It’s the only option when our goal is to support code generation for a new framework or language, but we’ll not cover it here.

For now, all we need is to change a single template, which is the second option. The first step, then, is to find this template. The official documentation recommends using the CLI version of the tool to extract all templates for a given generator.

However, when using the Maven plugin, it’s usually more convenient to look it up directly on the GitHub repository. Notice that, to ensure compatibility, we’ve picked the source tree for the tag that corresponds to the plugin version in use.

In the resources folder, each sub-folder has templates used for a specific generator target. For Spring-based projects, the folder name is JavaSpring. There, we’ll find the Mustache templates used to render the server code. Most templates are named sensibly, so it’s not hard to figure out which one we need: apiDelegate.mustache.

4.2. Template Customization

Once we’ve located the templates we want to customize, the next step is to place them in our project so the Maven plugin can use them. We’ll put the soon-to-customize template under the folder src/templates/JavaSpring so that it doesn’t get mixed with other sources or resources.

Next, we need to add a configuration option to the plugin informing about our directory:

<configuration>
    <inputSpec>${project.basedir}/src/main/resources/api/quotes.yaml</inputSpec>
    <generatorName>spring</generatorName>
    <supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
    <templateResourcePath>${project.basedir}/src/templates/JavaSpring</templateResourcePath>
    ... other unchanged properties omitted
</configuration>

To verify that the generator is correctly configured, let’s add a comment on top of the template and re-generate the code:

/*
* Generated code: do not modify !
* Custom template with support for x-spring-cacheable extension
*/
package {{package}};
... more template code omitted

Next, running mvn clean generate-sources will yield a new version of the QuotesDelegateApi with the comment:

/*
* Generated code: do not modify!
* Custom template with support for x-spring-cacheable extension
*/
package com.baeldung.tutorials.openapi.quotes.api;

... more code omitted

This shows that the generator picked our custom template instead of the native one.

4.3. Exploring the Base Template

Now, let’s take a look at our template to find the proper place to add our customization. We can see that there’s a section defined by the {{#operation}} {{/operation}} tags that outputs the delegate’s methods in the rendered class:

    {{#operation}}
        // ... many mustache tags omitted
        {{#jdk8-default-interface}}default // ... more template logic omitted 

    {{/operation}}

Inside this section, the template uses several properties of the current context – an operation – to generate the corresponding method’s declaration.

In particular, we can find information about vendor extensions under {{vendorExtension}}. This is a map where the keys are extension names, and the value is a direct representation of whatever data we’ve put in the definition. This means we can use extensions where the value is an arbitrary object or just a simple string.

To get a JSON representation of the full data structure that the generator passes to the template engine, add the following globalProperties element to the plugin’s configuration:

<configuration>
    <inputSpec>${project.basedir}/src/main/resources/api/quotes.yaml</inputSpec>
    <generatorName>spring</generatorName>
    <supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
    <templateResourcePath>${project.basedir}/src/templates/JavaSpring</templateResourcePath>
    <globalProperties>
        <debugOpenAPI>true</debugOpenAPI>
        <debugOperations>true</debugOperations>
    </globalProperties>
...more configuration options omitted

Now, when we run mvn generate-sources again, the output will have this JSON representation right after the message ## Operation Info##:

[INFO] ############ Operation info ############
[ {
  "appVersion" : "1.0.0",
... many, many lines of JSON omitted

4.4. Adding @Cacheable to Operations

We’re now ready to add the required logic to support caching operation results. One aspect that might be useful is to allow users to specify a cache name, but not require them to do so.

To support this requirement, we’ll support two variants of our vendor extension. If the value is simply true, a default cache name will be used:

paths:
  /some/path:
    get:
      operationId: getSomething
      x-spring-cacheable: true

Otherwise, it will expect an object with a name property that we’ll use as the cache name:

paths:
  /some/path:
    get:
      operationId: getSomething
      x-spring-cacheable:
        name: mycache

This is how the modified template looks with the required logic to support both variants:

{{#vendorExtensions.x-spring-cacheable}}
@org.springframework.cache.annotation.Cacheable({{#name}}"{{.}}"{{/name}}{{^name}}"default"{{/name}})
{{/vendorExtensions.x-spring-cacheable}}
{{#jdk8-default-interface}}default // ... template logic omitted 

We’ve added the logic to add the annotation right before the method’s signature definition. Notice the use of {{#vendorExtensions.x-spring-cacheable}} to access the extension value. According to Mustache rules, the inner code will be executed only if the value is “truthy”, meaning something that evaluates to true in a Boolean context. Despite this somewhat loose definition, it works fine here and is quite readable.

As for the annotation itself, we’ve opted to use “default” for the default cache name. This allows us to further customize the cache, although the details on how to do this are outside the scope of this tutorial.

5. Using the Modified Template

Finally, let’s modify our API definition to use our extension:

... more definitions omitted
paths:
  /quotes/{symbol}:
    get:
      tags:
        - quotes
      summary: Get current quote for a security
      operationId: getQuote
      x-spring-cacheable: true
        name: get-quotes

Let’s run mvn generate-sources once again to create a new version of QuotesApiDelegate:

... other code omitted
@org.springframework.cache.annotation.Cacheable("get-quotes")
default ResponseEntity<QuoteResponse> getQuote(String symbol) {
... default method's body omitted

We see that the delegate interface now has the @Cacheable annotation. Moreover, we see that the cache name corresponds to the name attribute from the API definition.

Now, for this annotation to have any effect, we also need to add the @EnableCaching annotation to a @Configuration class or, as in our case, to the main class:

@SpringBootApplication
@EnableCaching
public class QuotesApplication {
    public static void main(String[] args) {
        SpringApplication.run(QuotesApplication.class, args);
    }
}

To verify that the cache is working as expected, let’s write an integration test that calls the API multiple times:

@Test
void whenGetQuoteMultipleTimes_thenResponseCached() {

    var quotes = IntStream.range(1, 10).boxed()
      .map((i) -> restTemplate.getForEntity("http://localhost:" + port + "/quotes/BAEL", QuoteResponse.class))
      .map(HttpEntity::getBody)
      .collect(Collectors.groupingBy((q -> q.hashCode()), Collectors.counting()));

    assertThat(quotes.size()).isEqualTo(1);
}

We expect all responses to return identical values, so we’ll collect them and group them by their hash codes. If all responses produce the same hash code, then the resulting map will have a single entry. Note that this strategy works because the generated model class implements the hashCode() method using all fields.

6. Conclusion

In this article, we’ve shown how to configure the OpenAPI Generator tool to use a custom template that adds support for a simple vendor extension.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)