DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • Aggregating REST APIs Calls Using Apache Camel

Trending

  • Start Coding With Google Cloud Workstations
  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  • How to Convert XLS to XLSX in Java
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  1. DZone
  2. Coding
  3. Frameworks
  4. Versioning a REST API With Spring Boot and Swagger

Versioning a REST API With Spring Boot and Swagger

In this article, you'll learn how to maintain multiple versions of your REST API by using the Spring Boot framework along with Swagger.

By 
Piotr Mińkowski user avatar
Piotr Mińkowski
·
Feb. 23, 18 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
120.3K Views

Join the DZone community and get the full member experience.

Join For Free

One thing’s for sure. If you don’t have to version your API, do not try to do that. However, sometimes you have to. Many of the most popular services like Twitter, Facebook, Netflix, or PayPal are versioning their REST APIs. The advantages and disadvantages of that approach are obvious. On the one hand, you don’t have to worry about making changes in your API even if many external clients and applications consume it. But on the other hand, you have to maintain different versions of API implementation in your code, which sometimes may be troublesome.

In this article, I’m going to show you how to maintain the several versions of REST APIs in your application in the most comfortable way. We will base this article on a sample application written on top of the Spring Boot framework and exposing API documentation using Swagger2 and SpringFox libraries.

Spring Boot does not provide any dedicated solutions for versioning APIs. The situation is different for the SpringFox Swagger2 library, which provides a grouping mechanism from version 2.8.0, which is perfect for generating documentation of versioned REST API.

I have already introduced Swagger2 together with Spring Boot application in one of my previous posts. In the article,  Microservices API Documentation with Swagger2, you may read how to use Swagger2 for generating API documentation for all the independent microservices and publishing it in one place – on the API Gateway.

Different Approaches to API Versioning

There are some different ways to provide API versioning in your application. The most popular of them are:

  1. Through a URI path – you include the version number in the URL path of the endpoint, for example, /api/v1/persons.
  2. Through query parameters – you pass the version number as a query parameter with a specified name, for example, /api/persons?version=1.
  3. Through custom HTTP headers – you define a new header that contains the version number in the request.
  4. Through a content negotiation – the version number is included in the “Accept” header together with the accepted content type. The request with cURL would look like the following:
curl -H "Accept: application/vnd.piomin.v1+json" http://localhost:8080/api/persons

The decision as to which of these approaches to implement in the application is up to you. We would discuss the advantages and disadvantages of every single approach, however, this is not the main purpose of this article. The main purpose is to show you how to implement versioning in Spring Boot applications and then publish the API documentation automatically using Swagger2. The sample application source code is available on GitHub. I have implemented two of the approaches described above – in point 1 and 4.

Enabling Swagger for Spring Boot

Swagger2 can be enabled in a Spring Boot application by including the SpringFox library. In fact, this is the suite of Java libraries used for automating the generation of machine and human readable specifications for JSON APIs written using the Spring Framework. It supports such formats as Swagger, RAML, and JSON API. To enable it for your application, include the following Maven dependencies in the project: 

  • io.springfox:springfox-swagger-ui 

  • io.springfox:springfox-swagger2

  • io.springfox:springfox-spring-web

Then you will have to annotate the main class with @EnableSwagger2 and define the Docker object. Docket is SpringFox’s primary configuration mechanism for Swagger 2.0. We will discuss the details about it in the next section along with the sample for each way of versioning an API.

Sample API

Our sample API is very simple. It exposes basic CRUD methods for the Personentity. There are three versions of the API available for external clients: 1.0, 1.1, and 1.2. In version 1.1, I have changed the method for updating the Person entity. In version 1.0, it was available under the /person path, while now it is available under the /person/{id} path. This is the only difference between versions 1.0 and 1.1. There is also one only difference in the API between versions 1.1 and 1.2. Instead of the field birthDate it returns age as the integer parameter. This change affects all the endpoints except DELETE /person/{id}. Now, let’s proceed to the implementation.

Versioning Using a URI Path

Here’s the full implementation of URI path versioning inside Spring @RestController.

@RestController
@RequestMapping("/person")
public class PersonController {

    @Autowired
    PersonMapper mapper;
    @Autowired
    PersonRepository repository;

    @PostMapping({"/v1.0", "/v1.1"})
    public PersonOld add(@RequestBody PersonOld person) {
        return (PersonOld) repository.add(person);
    }

    @PostMapping("/v1.2")
    public PersonCurrent add(@RequestBody PersonCurrent person) {
        return mapper.map((PersonOld) repository.add(person));
    }

    @PutMapping("/v1.0")
    @Deprecated
    public PersonOld update(@RequestBody PersonOld person) {
        return (PersonOld) repository.update(person);
    }

    @PutMapping("/v1.1/{id}")
    public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
        return (PersonOld) repository.update(person);
    }

    @PutMapping("/v1.2/{id}")
    public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
        return mapper.map((PersonOld) repository.update(person));
    }

    @GetMapping({"/v1.0/{id}", "/v1.1/{id}"})
    public PersonOld findByIdOld(@PathVariable("id") Long id) {
        return (PersonOld) repository.findById(id);
    }

    @GetMapping("/v1.2/{id}")
    public PersonCurrent findById(@PathVariable("id") Long id) {
        return mapper.map((PersonOld) repository.findById(id));
    }

    @DeleteMapping({"/v1.0/{id}", "/v1.1/{id}", "/v1.2/{id}"})
    public void delete(@PathVariable("id") Long id) {
        repository.delete(id);
    }

}

If you would like to have three different versions available in the single generated API specification you should declare three Docket @Beans – one per single version. In this case, the Swagger group concept, which has been already introduced by SpringFox, would be helpful for us. The reason this concept has been introduced is a necessity to support applications which require more than one Swagger resource listing. Usually, you need more than one resource listing in order to provide different versions of the same API. We can assign a group to every Docket just by invoking the groupName DSL method on it. Because different versions of the API method are implemented within the same controller, we have to distinguish them by declaring path regex matching the selected version. All other settings are standard.

@Bean
public Docket swaggerPersonApi10() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("person-api-1.0")
        .select()
            .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
            .paths(regex("/person/v1.0.*"))
        .build()
        .apiInfo(new ApiInfoBuilder().version("1.0").title("Person API").description("Documentation Person API v1.0").build());
}

@Bean
public Docket swaggerPersonApi11() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("person-api-1.1")
        .select()
            .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
            .paths(regex("/person/v1.1.*"))
        .build()
        .apiInfo(new ApiInfoBuilder().version("1.1").title("Person API").description("Documentation Person API v1.1").build());
}

@Bean
public Docket swaggerPersonApi12() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("person-api-1.2")
        .select()
            .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
            .paths(regex("/person/v1.2.*"))
        .build()
        .apiInfo(new ApiInfoBuilder().version("1.2").title("Person API").description("Documentation Person API v1.2").build());
}

Now, we may display Swagger UI for our API just by calling the URL in the web browser path /swagger-ui.html. You can switch between all available versions of the API as you can see in the picture below.

Image title

The specification is generated by the exact version of the API. Here’s documentation for version 1.0. Because the method PUT /person is annotated with @Deprecated it is crossed out on the generated HTML documentation page.

Image title

If you switch to the group person-api-1 you will see all the methods that containv1.1 in the path. Along with them, you may recognize the current version of the PUT method with the {id} field in the path.

Image title

When using documentation generated by Swagger, you may easily call every method after expanding it. Here’s the sample of calling the method PUT /person/{id} from what we implemented for version 1.2.

Image title

Versioning Using ‘Accept’ Header

To access the implementation of versioning with the ‘Accept’ header you should switch to the branch header. Here’s the full implementation of content negotiation using ‘Accept’ header versioning inside Spring @RestController.

@RestController
@RequestMapping("/person")
public class PersonController {

    @Autowired
    PersonMapper mapper;
    @Autowired
    PersonRepository repository;

    @PostMapping(produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json"})
    public PersonOld add(@RequestBody PersonOld person) {
        return (PersonOld) repository.add(person);
    }

    @PostMapping(produces = "application/vnd.piomin.app-v1.2+json")
    public PersonCurrent add(@RequestBody PersonCurrent person) {
        return mapper.map((PersonOld) repository.add(person));
    }

    @PutMapping(produces = "application/vnd.piomin.app-v1.0+json")
    @Deprecated
    public PersonOld update(@RequestBody PersonOld person) {
        return (PersonOld) repository.update(person);
    }

    @PutMapping(value = "/{id}", produces = "application/vnd.piomin.app-v1.1+json")
    public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
        return (PersonOld) repository.update(person);
    }

    @PutMapping(value = "/{id}", produces = "application/vnd.piomin.app-v1.2+json")
    public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
        return mapper.map((PersonOld) repository.update(person));
    }

    @GetMapping(name = "findByIdOld", value = "/{idOld}", produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json"})
    @Deprecated
    public PersonOld findByIdOld(@PathVariable("idOld") Long id) {
        return (PersonOld) repository.findById(id);
    }

    @GetMapping(name = "findById", value = "/{id}", produces = "application/vnd.piomin.app-v1.2+json")
    public PersonCurrent findById(@PathVariable("id") Long id) {
        return mapper.map((PersonOld) repository.findById(id));
    }

    @DeleteMapping(value = "/{id}", produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json", "application/vnd.piomin.app-v1.2+json"})
    public void delete(@PathVariable("id") Long id) {
        repository.delete(id);
    }

}

We still have to define three Docker @Beans, but the filtering criteria are slightly different. Simply filtering by path is not an option here. We have to create Predicate for the RequestHandler object and pass it to the apis DSL method. The predicate implementation should filter every method in order to find only those which have the produces field with the required version number. Here’s a sample Docket implementation for version 1.2.

@Bean
public Docket swaggerPersonApi12() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("person-api-1.2")
        .select()
            .apis(p -> {
                if (p.produces() != null) {
                    for (MediaType mt : p.produces()) {
                        if (mt.toString().equals("application/vnd.piomin.app-v1.2+json")) {
                            return true;
                        }
                    }
                }
                return false;
            })
        .build()
        .produces(Collections.singleton("application/vnd.piomin.app-v1.2+json"))
        .apiInfo(new ApiInfoBuilder().version("1.2").title("Person API").description("Documentation Person API v1.2").build());
}

As you can see in the picture below, the generated methods do not have the version number in the path.

Image title

When calling a method for the selected version of API the only difference is in the response’s required content type.

Image title

Summary

Versioning is one of the most important concepts around HTTP API designing. No matter which approach to versioning you choose, you should do everything you can to describe your API well. This seems to be especially important in the era of microservices, where your interface may be called by many other independent applications. In this case, creating documentation in isolation from the source code could be troublesome. Swagger solves all of the described problems. It may be easily integrated with your application and it supports versioning. Thanks to the SpringFox project, it also can be easily customized in your Spring Boot application to meet more advanced demands.

API Spring Framework Spring Boot REST Web Protocols application

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • Aggregating REST APIs Calls Using Apache Camel

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!