API-First Development with Spring Boot and Swagger

Table Of Contents

Following an API-first approach, we specify an API before we start coding. Via API description languages, teams can collaborate without having implemented anything, yet.

Those description languages specify endpoints, security schemas, object schemas, and much more. Moreover, most of the time we can also generate code such a specification.

Often, an API specification also becomes the documentation of the API.

Example Code

This article is accompanied by a working code example on GitHub.

Benefits of API-First

To start working on an integration between components or systems, a team needs a contract. In our case, the contract is the API specification. API-first helps teams to communicate with each other, without implementing a thing. It also enables teams to work in parallel.

Where the API-first approach shines is on building a better API. Focusing on the functionality that it is needed to provide and only that. Minimalistic APIs mean less code to maintain.

Creating an API Spec with the Swagger Editor

Let’s create our own OpenAPI specification in a YAML document. To make it easier to follow, we’ll split the discussion into separate parts of the YAML document we’re creating.

If you want to learn more details about the OpenAPI-Specification you can visit the Github repository.

General Information

We start with some general information about our API at the top of our document:

openapi: 3.0.2
  title: Reflectoring
  description: "Tutorials on Spring Boot and Java."
  termsOfService: http://swagger.io/terms/
    email: petros.stergioulas94@gmail.com
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 0.0.1-SNAPSHOT
  description: Find out more about Reflectoring
  url: https://reflectoring.io/about/
- url: https://reflectoring.swagger.io/v2

The openapi field allows us to define the version of the OpenAPI spec that our document follows.

Within the info section, we add some information about our API. The fields should be pretty self-explanatory.

Finally, in the servers section, we provide a list of servers that implement the API.


Then comes some additional metadata about our API:

- name: user
  description: Operations about user
    description: Find out more about our store
    url: http://swagger.io

The tags section provides fields for additional metadata which we can use to make our API more readable and easier to follow. We can add multiple tags, but each tag should be unique.


Next, we’ll describe some paths. A path holds information about an individual endpoint and its operations:

      - user
      summary: Get user by user name
      operationId: getUserByName
      - name: username
        in: path
        description: 'The name that needs to be fetched. '
        required: true
          type: string
          description: successful operation
                $ref: '#/components/schemas/User'
          description: User not found
          content: {}

The $ref field allows us to refer to objects in a self-defined schema. In this case we refer to the User schema object (see the next section about Components).

The summary is a short description of what the operation does.

With the operationId, we can define a unique identifier for the operation. We can think about it as our method name.

Finally, the responses object allows us to define the outcomes of an operation. We must define at least one successful response code for any operation call.


The objects of the API are all described in the components section. The objects defined within the components object will not affect the API unless they are explicitly referenced from properties outside the components object, as we have seen above:

      type: object
          type: integer
          format: int64
          type: string
          type: string
        ... more attributes
          type: integer
          description: User Status
          format: int32
      type: oauth2
          authorizationUrl: http://reflectoring.swagger.io/oauth/dialog
            write:users: modify users
            read:users: read users
      type: apiKey
      name: api_key
      in: header

The schemas section allows us to define the objects we want to use in our API.

In the securitySchemes section, we can define security schemes that can be used by the operations.

There two possible ways to make use of security schemes.

First, we can add a security scheme to a specific operation using the security field:

      - user
      summary: Get user by user name
        - api_key: []

In the above example we explicitly specify that the path /user/{username} is secured with the api_key scheme we defined above.

However, if we want to apply security on the whole project, we just need to specify it as a top-level field:

      - user
      summary: Get user by user name
  - api_key: []

Now, all of our paths should be secured with the api_key scheme.

Generating Code From an API Specification

Having defined an API, we’ll now create code from the YAML document above.

We’ll take a look at two different approaches to generating the code:

Generating Code from Swagger Editor

Although this is an approach that I wouldn’t take, let’s talk about it and discuss why I think it’s a bad idea.

Let’s go over to Swagger Editor and paste our YAML file into it. Then, we select Generate Server from the menu and pick what kind of a server we’d like to generate (I went with “Spring”).

So why is this a bad idea?

First, the code that was generated for me is using Java 7 and Spring Boot 1.5.22, both of which are quite outdated.

Second, if we make a change to the specification (and changes happen all the time), we’d have to copy-and-paste the files that were changed manually.

Generating Code with the OpenAPI Maven plugin

A better alternative is to generate the code from within a Maven build with the OpenAPI Maven plugin.

Let’s take a look at the folder structure. I chose to use a multi-module maven project, where we have two projects:

  • app, an application that implements the API from our specification.
  • specification, whose only job is to provide the API Specification for our app.

The folder structure looks like this:

├── app
│   └── pom.xml
│   └── src
│       └── main
│           └── java
│               └── io.reflectoring
│                   └── OpenAPIConsumerApp.java
├── specification
│   └── pom.xml
│   └── src
│       └── resources
│           └── openapi.yml
└── pom.xml

For the sake of simplicity, we omit the test folders.

Our app is a simple Spring Boot project that we can automatically generate on start.spring.io, so let’s focus on the pom.xml from the specification module, where we configure the OpenAPI Maven plugin:


You can see the full pom.xml file on GitHub.

For this tutorial, we’re using the spring generator.

Simply running the command ./mvnw install will generate code that implements our OpenAPI specification!

Taking a look into the folder target/generated-sources/openapi/src/main/java/io/reflectoring/model, we find the code for the User model we defined in our YAML:

public class User   {
  private Long id;

  private String username;

  private String firstName;
  // ... more properties

  private Integer userStatus;

  // ... getters and setters


The generator does not only generate the models but also the endpoints. Let’s take a quick look at what we generated:

public interface UserApiDelegate {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();

     * POST /user : Create user
     * Create user functionality
     * @param body Created user object (required)
     * @return successful operation (status code 200)
     * @see UserApi#createUser
    default ResponseEntity<Void> createUser(User body) {
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

  // ... omit deleteUser, getUserByName and updateUser

Of course, the generator cannot generate our business logic for us, but it does generate interfaces like UserApiDelegate above for us to implement.

It also creates a UserApi interface which delegates calls to UserApiDelegate:

@Api(value = "user", description = "the user API")
public interface UserApi {

    default UserApiDelegate getDelegate() {
        return new UserApiDelegate() {};

     * POST /user : Create user
     * Create user functionality
     * @param body Created user object (required)
     * @return successful operation (status code 200)
    @ApiOperation(value = "Create user", 
      nickname = "createUser", 
      notes = "Create user functionality", 
      tags={ "user", })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = "successful operation") })
    @RequestMapping(value = "/user",
        method = RequestMethod.POST)
    default ResponseEntity<Void> createUser(
      @ApiParam(value = "Created user object" ,required=true )  
      @RequestBody User body) {
        return getDelegate().createUser(body);
    // ... other methods omitted

The generator also creates a Spring controller for us that implements the UserApi interface:

public class UserApiController implements UserApi {

    private final UserApiDelegate delegate;

    public UserApiController(
      @Autowired(required = false) UserApiDelegate delegate) {
        this.delegate = Optional.ofNullable(delegate)
            .orElse(new UserApiDelegate() {});

    public UserApiDelegate getDelegate() {
        return delegate;

Spring will inject our implementation of UserApiDelegate into the controller’s constructor if it finds it in the application context. Otherwise, the default implementation will be used.

Let’s start our application and hit the GET endpoint /v2/user/{username}.

curl -I http://localhost:8080/v2/user/Petros
HTTP/1.1 501
Content-Length: 0

But why do we get a 501 response (Not Implemented)?

Because we did not implement the UserApiDelegate interface and the UserApiController used the default one, which returns HttpStatus.NOT_IMPLEMENTED.

Now let’s implement the UserApiDelegate:

public class UserApiDelegateImpl implements UserApiDelegate {

    public ResponseEntity<User> getUserByName(String username) {
        User user = new User();
        // ... omit other initialization

        return ResponseEntity.ok(user);

It’s important to add a @Service or @Component annotation to the class so that Spring can pick it up and inject it into the UserApiController.

If we run curl http://localhost:8080/v2/user/Petros again now, we’ll receive a valid JSON response:

  "id": 123,
  "firstName": "Petros",
  // ... omit other properties

The UserApiDelegate is the single point of truth. That enables us to make fast changes in our API. For example, if we change the specification and generate it again, we only have to implement the newly generated methods.

The good thing is that if we won’t implement them, our application doesn’t break. By default, those endpoints would return HTTP status 501 (Not Implemented).

In my opinion, generating the OpenAPI Specification with Maven plugin instead of Swagger Editor is the better choice. That’s because we have more control over our options. The plugin provides some configuration and with Git as a version control tool, we can safely track any changes in either pom.xml and openapi.yml.


With OpenAPI we can create an API specification that we can share among teams to communicate contracts. The OpenAPI Maven plugin allows us to generate boilerplate code for Spring Boot from such a specification so that we only need to implement the business logic ourselves.

You can browse the example code on GitHub.

Written By:

Petros Stergioulas

Written By:

Petros Stergioulas

Recent Posts

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more

Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot

When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server.

Read more

One Stop Guide to Java Functional Interfaces

Introduction to Functional Programming Functional programming is a paradigm that focuses on the use of functions to create clear and concise code.

Read more