REST API versioning Archives - Piotr's TechBlog https://piotrminkowski.com/tag/rest-api-versioning/ Java, Spring, Kotlin, microservices, Kubernetes, containers Mon, 01 Dec 2025 10:59:41 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 REST API versioning Archives - Piotr's TechBlog https://piotrminkowski.com/tag/rest-api-versioning/ 32 32 181738725 Spring Boot Built-in API Versioning https://piotrminkowski.com/2025/12/01/spring-boot-built-in-api-versioning/ https://piotrminkowski.com/2025/12/01/spring-boot-built-in-api-versioning/#comments Mon, 01 Dec 2025 10:59:38 +0000 https://piotrminkowski.com/?p=15867 This article explains how to use Spring Boot built-in API versioning feature to expose different versions of REST endpoints. This is one of the most interesting updates introduced with Spring Boot 4. API versioning can be implemented using Spring Web’s standard REST API capabilities. If you’re interested in this approach, check out my somewhat outdated […]

The post Spring Boot Built-in API Versioning appeared first on Piotr's TechBlog.

]]>
This article explains how to use Spring Boot built-in API versioning feature to expose different versions of REST endpoints. This is one of the most interesting updates introduced with Spring Boot 4. API versioning can be implemented using Spring Web’s standard REST API capabilities. If you’re interested in this approach, check out my somewhat outdated article on the subject here.

Interestingly, the Micronaut framework also provides built-in API versioning. You can read more about it in the framework’s documentation here.

Source Code

Feel free to use my source code if you’d like to try it out yourself. To do that, you must clone my sample GitHub repository. Then you should only follow my instructions.

Introduction

The Spring Boot example application discussed in this article features two versions of the data model returned by the API. Below is the basic structure of the Person object, which is shared across all API versions.

public abstract class Person {

	private Long id;
	private String name;
	private Gender gender;

	public Person() {

	}
	
	public Person(Long id, String name, Gender gender) {
		this.id = id;
		this.name = name;
		this.gender = gender;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Gender getGender() {
		return gender;
	}

	public void setGender(Gender gender) {
		this.gender = gender;
	}

}
Java

The scenario assumes that we choose the option that returns the same age for a person in two different ways. This is a somewhat pessimistic version, but it is the one we want to examine. In the first method, we return JSON containing the birthdate. In the second method, we return the age field. Below is the PersonOld object implementing the first approach.

@Schema(name = "Person")
public class PersonOld extends Person {

	@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
	private LocalDate birthDate;

	public PersonOld() {

	}	
	
	public PersonOld(Long id, String name, Gender gender, LocalDate birthDate) {
		super(id, name, gender);
		this.birthDate = birthDate;
	}

	public LocalDate getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(LocalDate birthDate) {
		this.birthDate = birthDate;
	}

}
Java

Here, we see the PersonCurrent object, which contains the age field instead of the previously used birthDate.

@Schema(name = "Person")
public class PersonCurrent extends Person {

	private int age;

	public PersonCurrent() {

	}

	public PersonCurrent(Long id, String name, Gender gender, int age) {
		super(id, name, gender);
		this.age = age;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}
Java

Design API for Versioning with Spring Boot

API Methods

Now we can design an API that supports different object versions on one hand and two distinct versioning methods on the other. In the first method, we will use the HTTP header, and in the second, the request path. For clarity, below is a table of REST API methods for HTTP header-based versioning.

Method typeMethod pathDescription
POST/personsAdd a new person, v1.2 for PersonCurrent, v1.[0-1] for PersonOld
PUT/persons/{id}Update a person, v1.2 for PersonCurrent, v1.1 for PersonOld
DELETE/persons/{id}Delete a person
GET/persons/{id}Find a person by ID, v1.2 for PersonCurrent

Here, in turn, is a table for versioning based on the request path.

Method typeMethod pathDescription
POST/persons/v1.0, /persons/v1.1Add a new person (PersonOld)
POST/persons/v1.2Add a new person (PersonCurrent)
PUT/persons/v1.0Update a person – v1.0 deprecated
PUT/persons/v1.1/{id}Update a person with ID (PersonOld)
PUT/persons/v1.2/{id}Update a person with ID (PersonCurrent)
DELETE/persons/v1.0, /persons/v1.1, …Delete a person
GET/persons/v1.0/{id}, /persons/v1.1Find a person by ID, v1.0[1] for PersonOld
GET/persons/v1.2/{id}Find a person by ID, v1.2 for PersonCurrent

Spring Boot Implementation

To enable the built-in API versioning mechanism in Spring Web MVC, use spring.mvc.apiversion.* properties. The following configuration defines both of the API versioning methods mentioned above. In the header-based method, set its name. The header name used for testing purposes is api-version. In request path versioning, we must set the index of the path segment dedicated to the field with version. In our case, it is 1, because the version is read from the segment after the 0th element in the path, which is /persons. Please note that the two types of versions are only activated for testing purposes. Typically, you should select and use one API versioning method.

spring:
  mvc:
    apiversion:
      default: v1.0
      use:
        header: api-version
        path-segment: 1
Plaintext

Let’s continue by implementing individual API controllers. We use the @RestController approach for each versioning method. Now, in each annotation that specifies an HTTP method, we can include the version field. The mechanism maps the api-version header to the version field in the annotation. We can use syntax like v1.0+ to specify a version higher than v1.0.

@RestController
@RequestMapping("/persons-via-headers")
public class PersonControllerWithHeaders {

	@Autowired
	PersonMapper mapper;
	@Autowired
	PersonRepository repository;

	@PostMapping(version = "v1.0+")
	public PersonOld add(@RequestBody PersonOld person) {
		return (PersonOld) repository.add(person);
	}

	@PostMapping(version = "v1.2")
	public PersonCurrent add(@RequestBody PersonCurrent person) {
		return (PersonCurrent) repository.add(person);
	}
	
	@PutMapping(version = "v1.0")
	@Deprecated
	public PersonOld update(@RequestBody PersonOld person) {
		return (PersonOld) repository.update(person);
	}
	
	@PutMapping(value = "/{id}", version = "v1.1")
	public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
		return (PersonOld) repository.update(person);
	}
	
	@PutMapping(value = "/{id}", version = "v1.2")
	public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
		return mapper.map((PersonOld) repository.update(person));
	}
	
	@GetMapping(value = "/{id}", version = "v1.0+")
	public PersonOld findByIdOld(@PathVariable("id") Long id) {
		return (PersonOld) repository.findById(id);
	}

	@GetMapping(value = "/{id}", version = "v1.2")
	public PersonCurrent findById(@PathVariable("id") Long id) {
		return mapper.map((PersonOld) repository.findById(id));
	}
	
	@DeleteMapping("/{id}")
	public void delete(@PathVariable("id") Long id) {
		repository.delete(id);
	}
	
}
Java

Then, we can implement a similar approach, but this time based on the request path. Here’s our @RestController.

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

	@Autowired
	PersonMapper mapper;
	@Autowired
	PersonRepository repository;

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

	@PostMapping(value = "/{version}", version = "v1.2")
	public PersonCurrent add(@RequestBody PersonCurrent person) {
		return (PersonCurrent) repository.add(person);
	}
	
	@PutMapping(value = "/{version}", version = "v1.0")
	@Deprecated
	public PersonOld update(@RequestBody PersonOld person) {
		return (PersonOld) repository.update(person);
	}
	
	@PutMapping(value = "/{version}/{id}", version = "v1.1")
	public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
		return (PersonOld) repository.update(person);
	}
	
	@PutMapping(value = "/{version}/{id}", version = "v1.2")
	public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
		return mapper.map((PersonOld) repository.update(person));
	}
	
	@GetMapping(value = "/{version}/{id}", version = "v1.0+")
	public PersonOld findByIdOld(@PathVariable("id") Long id) {
		return (PersonOld) repository.findById(id);
	}
	
	@GetMapping(value = "/{version}/{id}", version = "v1.2")
	public PersonCurrent findById(@PathVariable("id") Long id) {
		return mapper.map((PersonOld) repository.findById(id));
	}
	
	@DeleteMapping(value = "/{version}/{id}", version = "v1.0+")
	public void delete(@PathVariable("id") Long id) {
		repository.delete(id);
	}
	
}
Java

Let’s start our application using the command below.

mvn spring-boot:run
ShellSession

We can test the REST endpoints of both controllers using the following curl commands. Below are the calls and the expected results.

$ curl http://localhost:8080/persons/v1.1/1
{"id":1,"name":"John Smith","gender":"MALE","birthDate":"1977-01-20"}

$ curl http://localhost:8080/persons/v1.2/1
{"id":1,"name":"John Smith","gender":"MALE","age":48}

$ curl -X POST http://localhost:8080/persons/v1.0 -d "{\"id\":1,\"name\":\"John Smith\",\"gender\":\"MALE\",\"birthDate\":\"1977-01-20\"}" -H "Content-Type: application/json"
{"id":6,"name":"John Smith","gender":"MALE","birthDate":"1977-01-20"}

$ curl -X POST http://localhost:8080/persons/v1.2 -d "{\"name\":\"John Smith\",\"gender\":\"MALE\",\"age\":40}" -H "Content-Type: application/json"
{"id":7,"name":"John Smith","gender":"MALE","age":40}
ShellSession

Testing API versioning with Spring Boot REST client

Importantly, Spring also offers support for versioning on the HTTP client side. This applies to both RestClient and WebClient, as well as their testing implementations. I don’t know if you’ve had a chance to use RestTestClient in your tests yet. After initializing the client instance, set the versioning method using apiVersionInserter. Then, when calling a given HTTP method, you can set the version number by calling apiVersion(...) with the version number as an argument. Below is a class that tests versioning using an HTTP header.

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PersonControllerWithHeadersTests {

    private WebApplicationContext context;
    private RestTestClient restTestClient;

    @BeforeEach
    public void setup(WebApplicationContext context) {
        restTestClient = RestTestClient.bindToApplicationContext(context)
                .baseUrl("/persons-via-headers")
                .apiVersionInserter(ApiVersionInserter.useHeader("api-version"))
                .build();
    }

    @Test
    @Order(1)
    void addV0() {
        restTestClient.post()
                .body(Instancio.create(PersonOld.class))
                .apiVersion("v1.0")
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(PersonOld.class)
                .value(personOld -> assertNotNull(personOld.getId()));
    }

    @Test
    @Order(2)
    void addV2() {
        restTestClient.post()
                .body(Instancio.create(PersonCurrent.class))
                .apiVersion("v1.2")
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }

    @Test
    @Order(3)
    void findByIdV0() {
        restTestClient.get()
                .uri("/{id}", 1)
                .apiVersion("v1.0")
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(PersonOld.class)
                .value(personOld -> assertNotNull(personOld.getId()));
    }

    @Test
    @Order(3)
    void findByIdV2() {
        restTestClient.get()
                .uri("/{id}", 2)
                .apiVersion("v1.2")
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }

    @Test
    @Order(3)
    void findByIdV2ToV1Compability() {
        restTestClient.get()
                .uri("/{id}", 1)
                .apiVersion("v1.2")
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }
}
Java

And here are similar tests, but this time for versioning based on the request path.

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PersonControllerTests {

    private WebApplicationContext context;
    private RestTestClient restTestClient;

    @BeforeEach
    public void setup(WebApplicationContext context) {
        restTestClient = RestTestClient.bindToApplicationContext(context)
                .baseUrl("/persons")
                .apiVersionInserter(ApiVersionInserter.usePathSegment(1))
                .build();
    }

    @Test
    @Order(1)
    void addV0() {
        restTestClient.post()
                .apiVersion("v1.1")
                .body(Instancio.create(PersonOld.class))
                .exchange()
                .expectBody(PersonOld.class)
                .value(personOld -> assertNotNull(personOld.getId()));
    }

    @Test
    @Order(2)
    void addV2() {
        restTestClient.post()
                .apiVersion("v1.2")
                .body(Instancio.create(PersonCurrent.class))
                .exchange()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }

    @Test
    @Order(3)
    void findByIdV0() {
        restTestClient.get().uri("/{id}", 1)
                .apiVersion("v1.0")
                .exchange()
                .expectBody(PersonOld.class)
                .value(personOld -> assertNotNull(personOld.getId()));
    }

    @Test
    @Order(3)
    void findByIdV2() {
        restTestClient.get().uri("/{id}", 2)
                .apiVersion("v1.2")
                .exchange()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }

    @Test
    @Order(3)
    void findByIdV2ToV1Compability() {
        restTestClient.get().uri("/{id}", 1)
                .apiVersion("v1.2")
                .exchange()
                .expectBody(PersonCurrent.class)
                .value(personCurrent -> assertNotNull(personCurrent.getId()))
                .value(personCurrent -> assertTrue(personCurrent.getAge() > 0));
    }

    @Test
    @Order(4)
    void delete() {
        restTestClient.delete().uri("/{id}", 5)
                .apiVersion("v1.2")
                .exchange()
                .expectStatus().is2xxSuccessful();
    }
}
Java

Here are my test results.

spring-boot-api-versioning-tests

OpenAPI for Spring Boot API versioning

I also tried to check what support for API versioning looks like on the Springdoc side. This project provides an OpenAPI implementation for Spring MVC. For Spring Boot 4, we must use at least 3.0.0 version of Springdoc.

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  <version>3.0.0</version>
</dependency>
XML

My goal was to divide the API into groups based on version for a path segment approach. Unfortunately, attempting this type of implementation results in an HTTP 400 response for both the /v3/api-docs and /swagger-ui.html URLs. That’s why I created an issue in Springdoc GitHub repository here. Once they fixed problems or eventually explain what I should improve in my implementation, I’ll update the article.

	@Bean
	public GroupedOpenApi personApiViaHeaders() {
		return GroupedOpenApi.builder()
				.group("person-via-headers")
				.pathsToMatch("/persons-via-headers/**")
				.build();
	}

	@Bean
	public GroupedOpenApi personApi10() {
		return GroupedOpenApi.builder()
				.group("person-api-1.0")
				.pathsToMatch("/persons/v1.0/**")
				.build();
	}

	@Bean
	public GroupedOpenApi personApi11() {
		return GroupedOpenApi.builder()
				.group("person-api-1.1")
				.pathsToMatch("/persons/v1.1/**")
				.build();
	}

	@Bean
	public GroupedOpenApi personApi12() {
		return GroupedOpenApi.builder()
				.group("person-api-1.2")
				.pathsToMatch("/persons/v1.2/**")
				.build();
	}
Java

Conclusion

Built-in API versioning support is one of the main features in Spring Boot 4. It works very smoothly. Importantly, API versioning is supported on both the server and client sides. We can also easily integrate it in JUnit tests with RestTestClient and WebTestClient. This article demonstrates Spring MVC implementation, but you can also use the built-in versioning API for Spring Boot applications based on the reactive WebFlux stack.

The post Spring Boot Built-in API Versioning appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2025/12/01/spring-boot-built-in-api-versioning/feed/ 1 15867
Versioning REST API with Spring Boot and Swagger https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/ https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/#respond Mon, 19 Feb 2018 14:26:04 +0000 https://piotrminkowski.wordpress.com/?p=6330 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. A large part of the most popular services like Twitter, Facebook, Netflix, or PayPal is versioning their REST APIs. The advantages and disadvantages of that approach are obvious. On the one hand, […]

The post Versioning REST API with Spring Boot and Swagger appeared first on Piotr's TechBlog.

]]>
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. A large part of the most popular services like Twitter, Facebook, Netflix, or PayPal is 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 maintained 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 several versions of the REST API in your application in the most comfortable way. We will base on the sample application written on the top of the Spring Boot framework and exposing API documentation using Swagger and SpringFox libraries.

Spring Boot does not provide any dedicated solutions for versioning APIs. The situation is different for SpringFox Swagger2 library, which provides 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 API Gateway.

Different approaches to API versioning

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

  1. Through an 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 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 to the “Accept” header together with accepted content type. The request with cURL would look like in the following sample: curl -H "Accept: application/vnd.piomin.v1+json" http://localhost:8080/api/persons

The decision, which of that approach implement in the application is up to you. We would discuss the advantages and disadvantages of every single approach, however it is not the main purpose of that article. The main purpose is to show you how to implement versioning in Spring Boot application and then publish the API documentation automatically using Swagger2. The sample application source code is available on GitHub (https://github.com/piomin/sample-api-versioning.git). I have implemented two of the approaches described above – in point 1 and 4.

Enabling Swagger for Spring Boot

Swagger2 can be enabled in Spring Boot application by including 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 Spring Framework. It supports such formats like swagger, RAML and jsonapi. To enable it for your application include the following Maven dependencies to 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 Docker object. Docket is a 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 API.

Sample API

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

Versioning using 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 for support applications that 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 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 URL in the web browser path /swagger-ui.html. You can switch between all available versions of API as you can see in the picture below.

api-1
Switching between available versions of API

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

api-2
Person API 1.0 specification

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

api-3
Person API 1.1 specification

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

api-5
Updating Person entity by calling method PUT from 1.2 version

Versioning using Accept header

To access the implementation of versioning witt ‘Accept’ header you should switch to branch header (https://github.com/piomin/sample-api-versioning/tree/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 criterias are slightly different. The simple filtering by path is not an option here. We have to crate Predicate for RequestHandler object and pass it to apis DSL method. The predicate implementation should filter every method in order to find only those which have produces field with required version number. Here’s 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.

api-6
Person API 1.2 specification for a content negotiation approach

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

api-7
Updating person and setting response content type

Summary

Versioning is one of the most important concepts around HTTP APIs designing. No matter which approaches to versioning you choose you should do everything 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, supports versioning. Thanks to the SpringFox project it also can be easily customized in your Spring Boot application to meet more advanced demands.

The post Versioning REST API with Spring Boot and Swagger appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/feed/ 0 6330