Overview

In this tutorial, we show you how to add versioning to REST API. In this example, we will look at 4 ways of versioning with Spring Boot RESTful services. URI Versioning, Request Parameter Versioning, Custom Request Header Versioning, Accept header Versioning.

Spring Boot RESTful Web Services Versioning Example

Prerequisites

  • Eclipse Oxygen and Install Spring Tool Suite for Eclipse IDE
  • spring-boot-starter-parent 2.1.0.BUILD-SNAPSHOT
  • spring-boot-starter-web
  • spring-boot-devtools
  • spring-boot-starter-test
  • Java 1.8+

Project Directory Structure

The following screenshot shows final structure of the project.

Spring Boot RESTful Web Services Versioning Example

Creating a Spring Boot Project with Eclipse STS

Launch Eclipse IDE. Go to File -> New -> Other... Select Spring Starter Project under Spring Boot category then click Next as shown below

Spring Boot RESTful Web Services Versioning Example
In the next screen, you enter the content as shown below then click Next
Spring Boot RESTful Web Services Versioning Example
Spring Boot RESTful Web Services Versioning Example


 Click Finish!

Project Dependencies

The updated pom.xml file will have the following code.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.jackrutorial</groupId>
 <artifactId>VersioningRESTfulServices</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>VersioningRESTfulServices</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.0.BUILD-SNAPSHOT</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

 <repositories>
  <repository>
   <id>spring-snapshots</id>
   <name>Spring Snapshots</name>
   <url>https://repo.spring.io/snapshot</url>
   <snapshots>
    <enabled>true</enabled>
   </snapshots>
  </repository>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/milestone</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </repository>
 </repositories>

 <pluginRepositories>
  <pluginRepository>
   <id>spring-snapshots</id>
   <name>Spring Snapshots</name>
   <url>https://repo.spring.io/snapshot</url>
   <snapshots>
    <enabled>true</enabled>
   </snapshots>
  </pluginRepository>
  <pluginRepository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/milestone</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </pluginRepository>
 </pluginRepositories>


</project>

VersioningResTfulServicesApplication.java

package com.jackrutorial;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class VersioningResTfulServicesApplication {

 public static void main(String[] args) {
  SpringApplication.run(VersioningResTfulServicesApplication.class, args);
 }
}

Model Layer

CustomerVersioning1.java

package com.jackrutorial.model;

public class CustomerVersioning1 {
 private String fullName;
 private String email;
 
 public String getFullName() {
  return fullName;
 }
 public void setFullName(String fullName) {
  this.fullName = fullName;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
}

CustomerVersioning2.java

package com.jackrutorial.model;

public class CustomerVersioning2 {
 private String fullName;
 private Email email;
 
 public String getFullName() {
  return fullName;
 }
 public void setFullName(String fullName) {
  this.fullName = fullName;
 }
 public Email getEmail() {
  return email;
 }
 public void setEmail(Email email) {
  this.email = email;
 }
}

Email.java

package com.jackrutorial.model;

public class Email {
 private String emailFirst;
 private String emailSecond;
 
 public String getEmailFirst() {
  return emailFirst;
 }
 public void setEmailFirst(String emailFirst) {
  this.emailFirst = emailFirst;
 }
 public String getEmailSecond() {
  return emailSecond;
 }
 public void setEmailSecond(String emailSecond) {
  this.emailSecond = emailSecond;
 }
}

Rest Controller

CustomerController.java

package com.jackrutorial.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.jackrutorial.model.CustomerVersioning1;
import com.jackrutorial.model.CustomerVersioning2;
import com.jackrutorial.model.Email;


@RestController
@RequestMapping("/customer")
public class CustomerController {
 
 //Uri Versioning
 @GetMapping({"/v1.0", "/v1.1"})
 public CustomerVersioning1 getCustomerV1ByUriVersioning() {
  return generateDataCustomerV1();
 }
 
 @GetMapping({"/v2.0", "/v2.1"})
 public CustomerVersioning2 getCustomerV2ByUriVersioning() {
  return generateDataCustomerV2();
 }
 
 //Request Parameter Versioning
 @GetMapping(value = "/param", params = "v=1")
 public CustomerVersioning1 getCustomerV1ByRequestParameterVersioning() {
  return generateDataCustomerV1();
 }
 
 @GetMapping(value = "/param", params = "v=2")
 public CustomerVersioning2 getCustomerV2ByRequestParameterVersioning() {
  return generateDataCustomerV2();
 }
 
 //Custom HTTP headers
 @GetMapping(value = "/customHeader", headers = "X-API-V=1")
 public CustomerVersioning1 getCustomerV1ByCustomHTTPHeaders() {
  return generateDataCustomerV1();
 }
 
 @GetMapping(value = "/customHeader", headers = "X-API-V=2")
 public CustomerVersioning2 getCustomerV2ByCustomHTTPHeaders() {
  return generateDataCustomerV2();
 }
 
 //Media Type Versioning
 @GetMapping(value = "/produces", produces = "application/vnd.jackrutorial.app-v1+json")
 public CustomerVersioning1 getCustomerV1ByMediaTypeVersioning() {
  return generateDataCustomerV1();
 }
 
 @GetMapping(value = "/produces", produces = "application/vnd.jackrutorial.app-v2+json")
 public CustomerVersioning2 getCustomerV2ByMediaTypeVersioning() {
  return generateDataCustomerV2();
 }
 
 //Generate Data
 private CustomerVersioning1 generateDataCustomerV1() {
  CustomerVersioning1 customer = new CustomerVersioning1();
  customer.setFullName("Test 1");
  customer.setEmail("admin@jackrutorial.com");
  
  return customer;
 }
 
 private CustomerVersioning2 generateDataCustomerV2() {
  CustomerVersioning2 customer = new CustomerVersioning2();
  customer.setFullName("Test 2");
  
  Email email = new Email();
  email.setEmailFirst("test1@jackrutorial.com");
  email.setEmailSecond("test2@jackrutorial.com");
  
  customer.setEmail(email);
  
  return customer;
 }
}

Run Spring Boot Application

Right click to the Project and follow the below steps:
  • select Run As -> Maven clean.
  • select Run As -> Maven install.
  • select Run As -> Spring Boot App.
View console output in eclipse, you will see following output:
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v2.1.0.BUILD-SNAPSHOT)

INFO 11236 --- [  restartedMain] c.j.VersioningResTfulServicesApplication : No active profile set, falling back to default profiles: default
INFO 11236 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
INFO 11236 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
INFO 11236 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
INFO 11236 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
INFO 11236 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.12
INFO 11236 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
INFO 11236 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1087 ms
INFO 11236 --- [  restartedMain] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
INFO 11236 --- [  restartedMain] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
INFO 11236 --- [  restartedMain] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
INFO 11236 --- [  restartedMain] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'formContentFilter' to: [/*]
INFO 11236 --- [  restartedMain] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
INFO 11236 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
INFO 11236 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
INFO 11236 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
INFO 11236 --- [  restartedMain] c.j.VersioningResTfulServicesApplication : Started VersioningResTfulServicesApplication in 1.836 seconds (JVM running for 2.895)
INFO 11236 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 11236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
INFO 11236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms

Demo

We will use the Postman tool to test the RESTFul Web Services Versioning.

1. Uri Versioning Examples

Request Method: GET
Enter Request URL: http://localhost:8080/customer/v1.0

Uri Versioning Examples


Response:
{
    "fullName": "Test 1",
    "email": "admin@jackrutorial.com"
}

Request Method: GET
Enter Request URL: http://localhost:8080/customer/v2.0

Uri Versioning Examples


Response:
{
    "fullName": "Test 2",
    "email": {
        "emailFirst": "test1@jackrutorial.com",
        "emailSecond": "test2@jackrutorial.com"
    }
}
 

2. Request Parameter Versioning Example

Request Method: GET
Enter Request URL: http://localhost:8080/customer/param?v=1

Request Parameter Versioning Example


Response:
{
    "fullName": "Test 1",
    "email": "admin@jackrutorial.com"
}

Request Method: GET
Enter Request URL: http://localhost:8080/customer/param?v=2

Request Parameter Versioning Example


Response:
{
    "fullName": "Test 2",
    "email": {
        "emailFirst": "test1@jackrutorial.com",
        "emailSecond": "test2@jackrutorial.com"
    }
}

3. Custom Request Header Versioning Example

Request Method: GET
Enter Request URL: http://localhost:8080/customer/customHeader
headers=[X-API-V=1]
  • HeaderName: X-API-V
  • HeaderValue: 1

Custom Request Header Versioning Example


Response:
{
    "fullName": "Test 1",
    "email": "admin@jackrutorial.com"
}


Enter Request URL: http://localhost:8080/customer/customHeader
headers=[X-API-V=2]
  • HeaderName: X-API-V
  • HeaderValue: 2

Custom Request Header Versioning Example


Response:
{
    "fullName": "Test 2",
    "email": {
        "emailFirst": "test1@jackrutorial.com",
        "emailSecond": "test2@jackrutorial.com"
    }
}

4. Media Type Versioning Example

Request Method: GET
Enter Request URL: http://localhost:8080/customer/produces
headers=[Accept=application/vnd.jackrutorial.app-v1+json]
  • HeaderName: Accept
  • HeaderValue: application/vnd.jackrutorial.app-v1+json

Media Type Versioning Example


Response:
{
    "fullName": "Test 1",
    "email": "admin@jackrutorial.com"
}

Request Method: GET
Enter Request URL: http://localhost:8080/customer/produces
headers=[Accept=application/vnd.jackrutorial.app-v2+json]
  • HeaderName: Accept
  • HeaderValue: application/vnd.jackrutorial.app-v2+json

Media Type Versioning Example


Response:
{
    "fullName": "Test 2",
    "email": {
        "emailFirst": "test1@jackrutorial.com",
        "emailSecond": "test2@jackrutorial.com"
    }
}
Previous Post
Next Post

post written by: