Course – LS – All

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

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll see how to map dates with OpenAPI. We’ll learn how to handle various date formats.

Two different Maven plugins allow the generation of the code from an OpenAPI specification: swagger-codegen and openapi-generator. We’ll discuss how to use them both.

2. Example Setup

First, let’s set up an example. We’ll write our initial YAML file and the base configuration for the Maven plugins.

2.1. Base YAML File

We’ll use a YAML file to describe our API. Let’s note that we’ll use the third version of the OpenAPI specification.

We’ll need to add a title and version for our file to comply with the specification. Besides, we’ll leave the paths section empty. However, in the components section, we’ll define an Event object which will have only one property for the moment, namely its organizer:

openapi: 3.0.0
info:
  title: an example api with dates
  version: 0.1.0
paths:
components:
  schemas:
    Event:
      type: object
      properties:
        organizer:
          type: string

2.2. swagger-codegen Plugin Configuration

The latest version of the swagger-codegen plugin can be found in the Maven Central Repository. Let’s start with the base configuration of the plugin:

<plugin>
    <groupId>io.swagger.codegen.v3</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>3.0.52</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/static/event.yaml</inputSpec>
                <language>spring</language>
                <configOptions>
                    <java8>true</java8>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

We can now execute the plugin:

mvn clean compile

The Event class is generated according to the OpenAPI specification, with its constructor, getters, and setters. Its equals(), hashcode(), and toString() method are also overridden.

2.3. openapi-generator Plugin Configuration

Similarly, the latest version of the openapi-generator plugin is available in the Maven Central Repository. Let’s now do the base configuration for it:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.2.1</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <skipValidateSpec>true</skipValidateSpec>
                <inputSpec>${project.basedir}/src/main/resources/static/event.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <configOptions>
                    <java8>true</java8>
                    <openApiNullable>false</openApiNullable>
                    <interfaceOnly>true</interfaceOnly>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

Having an empty paths section in our YAML file is fine with respect to the OpenAPI specification. However, openapi-generator would refuse it by default. Thus, we’ve set the skipValidateSpec flag to true.

We’ve also set the openApiNullable property to false in our options list because, otherwise, the plugin would require us to add a dependency to jackson-databing-nullable that we don’t need for our purpose.

We’ve also set interfaceOnly to true, mainly to avoid the generation of unnecessary Spring Boot integration tests.

In this case, also, running the compile Maven phase generates the Event class with all the methods.

3. OpenAPI Standard Date Mapping

OpenAPI defines several basic data types: string is one of them. Within the string data type, OpenAPI defines two default formats to handle dates: date and date-time.

3.1. date

The date format refers to the full-date notation defined by RFC 3339, section 5.6. For instance, 2023-02-08 is such a date.

Let’s now add a startDate property of date format to our Event definition:

startDate:
  type: string
  format: date

We don’t need to update the configuration of the Maven plugins. Let’s generate the Event class again. We can see that a new property appears in the generated file:

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

The main difference between the two plugins is that swagger-codegen doesn’t annotate the startDate property with @DateTimeFormat. Both plugins also create the associated getter, setter, and constructor in the same way.

As we can see, the default behavior of the generators is to use the LocalDate class for the date format.

3.2. date-time

The date-time format refers to the date-time notation defined by RFC 3339, section 5.6. For example, 2023-02-08T18:04:28Z matches this format.

Let’s now add an endDate property of date-time format to our Event:

endDate:
  type: string
  format: date-time

Once more, we don’t need to modify the configuration of any of the plugins. When we generate the Event class again, a new property shows up:

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

The remark we made with the date format is still valid: contrary to openapi-generator, swagger-codegen doesn’t annotate the property with @DateTimeFormat. Furthermore, the plugins create the associated getter, setter, and constructor.

We can see that the generators default to the OffsetDateTime class to represent the date-time format.

4. Use Other Standard Date Classes

Instead of generating the default classes, we’ll now force the plugins to use a certain class for each format.

Let’s edit the swagger-codegen Maven plugin configuration:

<configuration>
    <inputSpec>${project.basedir}/src/main/resources/static/event.yaml</inputSpec>
    <language>spring</language>
    <configOptions>
        <java8>true</java8>
        <dateLibrary>custom</dateLibrary>
    </configOptions>
    <typeMappings>
        <typeMapping>DateTime=Instant</typeMapping>
        <typeMapping>Date=Date</typeMapping>
    </typeMappings>
    <importMappings>
        <importMapping>Instant=java.time.Instant</importMapping>
        <importMapping>Date=java.util.Date</importMapping>
    </importMappings>
</configuration>

Let’s have a closer look at the new lines:

  • we use the dateLibrary option with the custom value: this means we’ll define our own date classes instead of using the standard ones
  • in the importMappings section, we tell the plugin to import the Instant and Date class and tell it exactly where to look them up
  • the typeMappings section is where all the magic happens: we tell the plugin to use Instant to handle the date-time format, and Date to handle the date format

For openapi-generator, we need to add the exact same lines in the exact same locations. The result is just slightly different because we had defined more options to begin with:

<configuration>
    <skipValidateSpec>true</skipValidateSpec>
    <inputSpec>${project.basedir}/src/main/resources/static/event.yaml</inputSpec>
    <generatorName>spring</generatorName>
    <configOptions>
        <java8>true</java8>
        <dateLibrary>custom</dateLibrary>
        <openApiNullable>false</openApiNullable>
        <interfaceOnly>true</interfaceOnly>
    </configOptions>
    <typeMappings>
        <typeMapping>DateTime=Instant</typeMapping>
        <typeMapping>Date=Date</typeMapping>
    </typeMappings>
    <importMappings>
        <importMapping>Instant=java.time.Instant</importMapping>
        <importMapping>Date=java.util.Date</importMapping>
    </importMappings>
</configuration>

Let’s now generate the files and have a look at them:

import java.time.Instant;
import java.util.Date;

(...)
    @JsonProperty("startDate")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private Date startDate;

    @JsonProperty("endDate")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private Instant endDate;

The plugin has indeed replaced the date format with a Date object and the date-time format with an Instant. As before, the only difference between both plugins is that swagger-codegen doesn’t annotate the properties with @DateTimeFormat.

Last, but not least, let’s note that there’s no validation whatsoever by the plugin. For instance, we could use the java.lang.Math class to model the date format, and the code would still be generated successfully.

5. Use a Custom Date Pattern

We’ll now discuss a last possibility. If, for some reason, we really can’t rely on any standard date API, we can always use String to handle our date. In that case, we’ll need to define the validation pattern we want the string to follow.

For example, let’s add a ticketSales date to our Event object specification. This ticketSales will be formatted as DD-MM-YYYY, like 18-07-2024:

ticketSales:
  type: string
  description: Beginning of the ticket sales
  example: "01-01-2023"
  pattern: "[0-9]{2}-[0-9]{2}-[0-9]{4}"

As we can see, we defined the regular expression that ticketSales must match. Let’s note that this pattern can’t distinguish between DD-MM-YYYY and MM-DD-YYYY. Additionally, we’ve added a description and an example for this field: since we don’t handle dates the standard way, an insight seems quite helpful.

We don’t need to make any changes to the plugins’ configurations. Let’s generate the Event class with openapi-generator:

@JsonProperty("ticketSales")
private String ticketSales;

(...)

/**
 * Beginning of the ticket sales
 * @return ticketSales
*/
@Pattern(regexp = "[0-9]{2}-[0-9]{2}-[0-9]{4}") 
@Schema(name = "ticketSales", example = "01-01-2023", description = "Beginning of the ticket sales", required = false)
public String getTicketSales() {
  return ticketSales;
}

As we can see, the getter is annotated with the defined Pattern. Thus, we need to add a dependency to javax.validation to make it work:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

The swagger-codegen plugin generates very similar code.

6. Conclusion

In this article, we’ve seen that both swagger-codegen and openapi-generator Maven plugins offer built-in formats for date and date-time handling. If we prefer to use other standards Java date APIs instead, we can override the configuration of the plugin. When we really can’t use any date API, we can always store our date as a String and manually specify the validation pattern.

As always, the code is available over on GitHub.

Course – LS – All

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

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.