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.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.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 belowIn the next screen, you enter the content as shown below then click Next
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.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: 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: GETEnter Request URL: http://localhost:8080/customer/v1.0
Response:
{ "fullName": "Test 1", "email": "admin@jackrutorial.com" }
Request Method: GET
Enter Request URL: http://localhost:8080/customer/v2.0
Response:
{ "fullName": "Test 2", "email": { "emailFirst": "test1@jackrutorial.com", "emailSecond": "test2@jackrutorial.com" } }
2. Request Parameter Versioning Example
Request Method: GETEnter Request URL: http://localhost:8080/customer/param?v=1
Response:
{ "fullName": "Test 1", "email": "admin@jackrutorial.com" }
Request Method: GET
Enter Request URL: http://localhost:8080/customer/param?v=2
Response:
{ "fullName": "Test 2", "email": { "emailFirst": "test1@jackrutorial.com", "emailSecond": "test2@jackrutorial.com" } }
3. Custom Request Header Versioning Example
Request Method: GETEnter Request URL: http://localhost:8080/customer/customHeader
headers=[X-API-V=1]
- HeaderName: X-API-V
- HeaderValue: 1
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
Response:
{ "fullName": "Test 2", "email": { "emailFirst": "test1@jackrutorial.com", "emailSecond": "test2@jackrutorial.com" } }
4. Media Type Versioning Example
Request Method: GETEnter Request URL: http://localhost:8080/customer/produces
headers=[Accept=application/vnd.jackrutorial.app-v1+json]
- HeaderName: Accept
- HeaderValue: application/vnd.jackrutorial.app-v1+json
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
Response:
{ "fullName": "Test 2", "email": { "emailFirst": "test1@jackrutorial.com", "emailSecond": "test2@jackrutorial.com" } }