The complete Traccar REST API is defined in a single openapi.yaml file and its first line defines the openapi: 3.1.0 specification. This file is used with the OpenAPI Generator to generate a plain Java Client from an OpenAPI specification. The generator is controlled and configured via openapi-generator-maven-plugin and produces a traccar-api-generated-x.y.z.jar (in two steps).

tl;dr

This project is actually only pointing to (or holding) the openapi-traccar-x.y.z.yaml file as source and is used to generate a Maven child project in the target/ folder and run this child project to produce the traccar-api-generated-x.y.z.jar.

In a clean state you can not see the child project and actually there is no need to open this traccar-openapitools-client project nor the created child project in your IDE to avoid extra configuration (for a non existing project). Once the Java Client is created and stored in your .m2 repository it will be picked up by higher layer projects, i.e. the traccar-api-client.

client abstraction

Since OpenAPI is an official specification there are many and will be more different code generators. Anyhow the resulting functionalities should remain the same. Therefore our client development will not directly invoke the server calls on the generated artifact. The higher level traccar-api-client is wrapped around the generated client (hiding it!), yet abstracting the server invocations with the bm.traccar.api.Api interface.

openapi.yaml file

The complete Traccar REST API is defined in a single openapi.yaml file to be found in the root folder of every release. The latest version is available at the
(1) Official Traccar API Reference by clicking the button "Download OpenAPI specification".
(2) https://www.traccar.org/api-reference/openapi.yaml
(3) https://raw.githubusercontent.com/traccar/traccar/master/openapi.yaml

In order to automate the versioning we will use this versioned url for the API generation
(4) https://github.com/traccar/traccar/blob/v6.8.0/openapi.yaml
which is part of the Traccar Software Distribution with each version.

<inputSpec>
    https://raw.githubusercontent.com/traccar/traccar/v${traccar.version}/openapi.yaml
</inputSpec>                                           ==================

openapi-generator-maven-plugin

When you run mvn compile, the Maven lifecycle passes the generate-sources phase. The plugin is configured to participate in that phase, and executes the Maven Plugin with the given configuration.

    <id>generate-client</id>
    <goals>
        <goal>generate</goal>
    </goals>

You can identify these tags in the build output:

openapi-generator-maven-plugin:7.13.0:generate (generate-client)
                                      ========  ===============

During the compile phase, all the code generated from the grammar files is compiled without further configuration.
You can check the General Configuration parameters for all options.

Basically we use the java generator with the restclient library

    <configuration>
        <inputSpec>path/to/${traccar.version}/openapi.yaml</inputSpec>
        <generatorName>java</generatorName>
            <configOptions>
                <library>restclient</library>
            </configOptions>

RestClient is a synchronous HTTP client introduced in Spring Framework 6.1 M2 that supersedes RestTemplate. A synchronous HTTP client sends and receives HTTP requests and responses in a blocking manner, meaning it waits for each request to complete before proceeding to the next one.

— see www.baeldung.com/spring-boot-restclient

and we define several package names to generate

        <invokerPackage>bm.traccar.invoke</invokerPackage>
            <apiPackage>bm.traccar.generated.api</apiPackage>
          <modelPackage>bm.traccar.generated.model.dto</modelPackage>

and we specify the artifact to be generated, i.e. the versioned name of the jar:

           <groupId>bm</groupId>
        <artifactId>traccar-api-generated</artifactId>

To keep things simple we do not post process (JAVA_POST_PROCESS_FILE) the result nor do we use the --model-name-mappings - as of now. Anyhow it is a good place to adopt the model to your company model.

two step processing

By running mvn install on project level

~/git/bm/bm-traccar/traccar-openapitools-client$ mvn install

you can idenfify the processing in two steps.

step 1 - traccar-openapitools-client

[INFO] --- openapi-generator-maven-plugin:7.13.0:generate (generate-client)
  :                                          @ traccar-openapitools-client ---
[INFO] OpenAPI Generator: java (client)
  :
[INFO] Processing operation null

This is raised because the traccar yaml file does not provide operationId for endpoints. These will be provided by the generator as described below.

Then the DTO are created in the target/generated-sources/openapi folder:

[INFO] writing file .../openapi/src/main/java/bm/traccar/generated/model/dto/Attribute.java
     generated project: openapi                generated DTOs in folder: dto

If you look into the newly generated openapi/ project you will find a completly generated maven project, which you can not find in the original sources. The openapi project comes with a lengthy pom.xml with the name and group we have defined earlier to define the final jar outputted. As a development guideline: we won’t go into the generated project, simply run it and use the output for subsequent projects.

Then the generator creates an operationId for every endpoint:

[WARNING] Empty operationId found for path: get /devices.
     Renamed to auto-generated operationId: devicesGet
                               =======================

Each endpoint and HTTP verb combination make an operation.

— see redocly.com/blog/operationid-is-api-design

In OpenAPI terms, paths are endpoints (resources), such as /users or /reports/summary/, that your API exposes, and operations are the HTTP methods used to manipulate these paths, such as GET, POST or DELETE.

— see swagger.io/docs/specification/v3_0/paths-and-operations/

These operationId are used to create api/ sources, tests and markdown docs

[INFO] writing file /openapi/src/main/java/bm/traccar/generated/api/AttributesApi.java
[INFO] writing file /openapi/src/test/java/bm/traccar/generated/api/AttributesApiTest.java
[INFO] writing file /openapi/docs/AttributesApi.md              ===

After running mvn install you can find the generated documents here

Finally the project generation is wrapped up with

[INFO] writing file /openapi/pom.xml
  :
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/ApiClient.java
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/ServerConfiguration.java
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/ServerVariable.java
  :
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/auth/HttpBasicAuth.java
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/auth/HttpBearerAuth.java
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/auth/ApiKeyAuth.java
[INFO] writing file /openapi/src/main/java/bm/traccar/invoke/auth/Authentication.java
  :

The rest of the maven build for this project is irrelevant:

[INFO] --- maven-compiler-plugin:3.1:compile     (default-compile) ---
[INFO] Not compiling main sources
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) ---
[INFO] No sources to compile
[INFO] --- maven-surefire-plugin:3.5.3:test      (default-test) ---
[INFO] No tests to run.
[INFO] --- maven-jar-plugin:2.4:jar              (default-jar) ---
[INFO] Building jar: traccar-openapitools-client-6.7.1.jar

This is achieved with

<maven.main.skip>true</maven.main.skip>

as there are no sources to process and test.

openapi.yaml

Traccar always has and still is evolving at a high pace and for this repository especially the REST API is vital. We are not interested in traccar server development directly, since we abstract the server with its REST API.

In the traccar-openapitools-client project we are accessing the versioned openapi.yaml from

<inputSpec>https://raw.githubusercontent.com/traccar/traccar/v${traccar.version}/openapi.yaml</inputSpec>

as the only dependency, i.e. interface to traccar.

  • ✓ The creation of the traccar-api-generated-x.y.z.jar is completly independant
    and only needs to be executec once.

nice2have:
skip generation, if jar exists in .m2.

openapi.yaml format

After you run traccar-openapitools-client you will find a

/traccar-openapitools-client/target/generated-sources/openapi/api/openapi.yaml

file. If you diff this file against the one downloaded you might be surprised. The file in the target is hundreds of lines longer with hundreds of differences. Do not be alarmed.

The new file is not a direct download of the Traccar spec; it’s a processed and resolved version of it.
The openapi-generator-maven-plugin parses, validates, and then rewrites it so that the internal generator engine has a clean, standardized map to build your code from.

If the original Traccar openapi.yaml uses $ref to point to other files, the generator resolves all of those links. It pulls all that external data into a single, massive, flat file. The generator often reorders keys to follow a strict internal logic. Even if your input has description before operationId, the generator might flip them.

step 2 - traccar-api-generated

So we have generated a new Java Maven Project traccar-api-generated
in the target/generated-sources/openapi folder - and not a client software - yet.

The newly generated project is not related or part of any predefined POM. To handle this we add the exec-maven-plugin to execute a complete mvn install to provide the generated jar for further processing. The execution is bound to the install phase

    <id>install-generated-client</id>
    <phase>install</phase>
    <goals>
        <goal>exec</goal>
    </goals>

which you can find in the build process

[INFO] --- exec-maven-plugin:3.5.0:exec (install-generated-client)
                                               @ traccar-openapitools-client ---
[INFO] ----------------------< bm:traccar-api-generated >----------------------
[INFO] Building traccar-api-generated 6.7.1
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] --- maven-enforcer-plugin:3.4.0:     .. @ traccar-api-generated ---
[INFO] --- build-helper-maven-plugin:3.4.0: .. @ traccar-api-generated ---
[INFO] --- maven-resources-plugin:2.6:      .. @ traccar-api-generated ---
[INFO] --- maven-compiler-plugin:3.11.0:    .. @ traccar-api-generated ---
[INFO] --- build-helper-maven-plugin:3.4.0  .. @ traccar-api-generated ---
[INFO] --- maven-resources-plugin:2.6:      .. @ traccar-api-generated ---
[INFO] --- maven-compiler-plugin:3.11.0:    .. @ traccar-api-generated ---
[INFO] --- maven-surefire-plugin:3.1.2:     .. @ traccar-api-generated ---
[INFO] --- maven-jar-plugin:3.3.0:jar       .. @ traccar-api-generated ---
[INFO] Building jar: .../openapi/target/traccar-api-generated-6.7.1.jar
[INFO]
[INFO] --- maven-dependency-plugin:2.8:     .. @ traccar-api-generated ---
[INFO] --- maven-jar-plugin:3.3.0:          .. @ traccar-api-generated ---
[INFO] Building jar: .../openapi/target/traccar-api-generated-6.7.1-tests.jar
[INFO]
[INFO] --- maven-javadoc-plugin:3.5.0:jar   .. @ traccar-api-generated ---
[INFO] Building jar: .../openapi/target/traccar-api-generated-6.7.1-javadoc.jar
[INFO]
[INFO] --- maven-source-plugin:3.3.0:       .. @ traccar-api-generated ---
[INFO] Building jar: .../openapi/target/traccar-api-generated-6.7.1-sources.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:        .. @ traccar-api-generated ---
[INFO] Installing   .../openapi/target/traccar-api-generated-6.7.1.jar
       to   .../.m2/repository/bm/traccar-api-generated/6.7.1/traccar-api-generated-6.7.1.jar

Note that the execution is triggered in the first project: @ traccar-openapitools-client
to launch the Building of the generated project: @ traccar-api-generated.

As you can see several jar files are being built and finally installed in your .m2/repository. Once the traccar-api-generated-x.y.z.jar is installed this project has served its purpose and only needs to be rerun for a new traccar release.

Another thing worth mentioning is that you will get an internal BUILD SUCCESS for the Maven execution kicked off from the main lifecycle:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.498 s
[INFO] Finished at: 2025-06-16T13:51:22+02:00
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  21.282 s
[INFO] Finished at: 2025-06-16T13:51:22+02:00
[INFO] ------------------------------------------------------------------------

javadocs

After generating the traccar-openapitools-client you can access the javadocs directly in the generated-sources/ folder.

Of course they will be lost again after a mvn clean, but then you still have the traccar-api-generated-6.7.1-javadoc.jar in your .m2!

Inconsistent OpenAPI Specification

Software is alive and the Traccar API, OpenAPI Specification and API generators are changeing continuously. As you can read in the Traccar Forum API Documentation inconsistencies can occur.

Model vs. API Parameter Generation

Let’s look at an example and see how we can handle or patch inconsistencies.

After the upgrade to Traccar 6.8.0 the tests ran into compile problems. Now the maintainer of this project should not propagate new problems to the project users and rather find a workaround for minimal code changes in the team.

[ERROR] UsersApiIT.java:[53,31] incompatible types: java.lang.Long cannot be converted to int
[ERROR] UsersApiIT.java:[62,39] incompatible types: java.lang.Long cannot be converted to java.lang.Integer

problem

It turns out that there is an inconsistency between Model and Method generation.
The yaml specifies a User object with an id:

    User:
      type: object
      properties:
        id: type: integer
          format: int64

which is generated by the model generator and correctly maps the User class and id of type Long:

    public class User {
      public static final String JSON_PROPERTY_ID = "id";
      @jakarta.annotation.Nullable
      private Long id;
              =======

The API method generator is responsible for creating the UserApi interface. When it processes the path parameter, such as users/{id}

  /users/{id}:
    delete:
      summary: Delete a User
      tags:
        - Users
      parameters:
        - name: id

it generates the (simplified) java method:

    public class UsersApi {
      /** Delete a User */
      private ResponseSpec usersIdDeleteRequestCreation(Integer id) { ..
                                                        ==========

A Java Developer expects type safe API for a type safe programming language. The int64 format in OpenAPI maps directly to a 64-bit integer, which should be represented by a long (or Long) in Java. The Integer class, on the other hand, corresponds to a 32-bit integer, which is the correct mapping for int32 format.

The correct configuration for the openapi-generator-maven-plugin is described as

    <configOptions>
        <typeMappings>int64=Long</typeMappings>
    </configOptions>

but it doesn’t work for (or hurt) the current version. So let’s just leave it there for coming releases.

solution

Still the maintainer has to come up with a suitable solution: Since large Long numbers don’t fit in an Integer we can add an explicit narrowing cast in the higher level traccar-api-client Api and catch a NumberFormatException or the like to create an ApiException with a usefull error message.

Important

As of Traccar 6.8.0 we will restrict the User id range to Integer,
which should be sufficient for most Traccar servers and databases.

The idea is to define the Api interface as intended. Then we can hope for the impovement of the traccar yaml and / or OpenAPI Generator and easily remove the workaround.

As we understand the intention of the widend value we will stick to the User.id type definition Long. Java does not allow to call a method( Long value ) with an Integer value. There is no implicit (widening) casting and the Long and Integer objects are not related. The conversion must be explicit.

In a first step the Api interface is changed to the intended signature

  interface Users {
    void deleteUser(Long id) throws ApiException;
                    ====

and then we can sneak in a type conversion on the implementation side

  Users users =
      new Api.Users() {
        @Override
        public void deleteUser(Long id) {
                               ====
          Integer integerId = ApiHelper.toInt(id);
          usersApi.usersIdDelete(integerId);
                                 =======
        }

The ApiHelper will take the Long, check if it fits into an Integer and call the generated client method. The implicit type test is done with the Math.toIntExact(longObj), which is the most reliable. If the cast fails due we have to through a meaningful exception message.

conclusion

This project should be a closed shop in the best case. Meaning that it is used to invoke a fully automated generation
of a REST Client Software traccar-api-generated-x.y.z.jar from the traccar specification openapi.yaml file.

Of course you can code on the basis of this jar, but it is recommended to use the higher level traccar-api-client project, which provides the Api interface and embeds the Java Client in a Spring @Service for simple handling and integration.