RESTful API Versioning in Spring Boot: Four Methods and Implementation
This tutorial explains why RESTful API versioning is needed, presents four versioning strategies (URI, request parameter, custom header, and media type), provides Spring Boot code examples for each, and discusses factors to consider when choosing a versioning approach.
Hello everyone, I am PiaoMiao!
In real-world project development we often need to manage API versioning. Today we will discuss why versioning is needed and how to version a REST API, covering four versioning methods and comparing them.
What you will learn from this article
Why we need version control for RESTful APIs?
What version control options are available?
How to implement RESTful version control?
Why we need RESTful API versioning
The best versioning strategy is not to version at all. If you don't need versioning, don't version.
Build backward‑compatible services to avoid versioning as much as possible!
However, in many cases versioning is required; let's look at concrete examples:
Initial version of the Student service returns JSON like:
{
"name": "Bob Charlie"
}Later we want to split the name, creating a new version that returns:
{
"name": {
"firstName": "Bob",
"lastName": "Charlie"
}
}You could support both requests from the same service, but as each version's requirements diversify, the service becomes increasingly complex, making versioning essential.
Next, let's create a simple Spring Boot Maven project and explore four different methods for versioning a RESTful service.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>Beans used for versioning
First version bean:
@Data
@AllArgsConstructor
public class StudentV1 {
private String name;
}Second version bean:
@Data
public class StudentV2 {
private Name name;
}Name entity used by StudentV2:
@Data
@AllArgsConstructor
public class Name {
private String firstName;
private String lastName;
}Methods for RESTful versioning
We want to create two service versions, one returning StudentV1 and the other StudentV2. Below are four different ways to achieve this.
URI versioning
@RestController
public class StudentUriController {
@GetMapping("v1/student")
public StudentV1 studentV1() {
return new StudentV1("javadaily");
}
@GetMapping("v2/student")
public StudentV2 studentV2() {
return new StudentV2(new Name("javadaily", "JAVA日知录"));
}
}Request: http://localhost:8080/v1/student → Response: {"name":"javadaily"}
Request: http://localhost:8080/v2/student → Response: {"name":{"firstName":"javadaily","lastName":"JAVA日知录"}}
Request‑parameter versioning
Versioning using a query parameter:
@RestController
public class StudentParmController {
@GetMapping(value="/student/param", params = "version=1")
public StudentV1 studentV1() {
return new StudentV1("javadaily");
}
@GetMapping(value="/student/param", params = "version=2")
public StudentV2 studentV2() {
return new StudentV2(new Name("javadaily", "JAVA日知录"));
}
}Request: http://localhost:8080/student/param?version=1 → Response: {"name":"javadaily"}
Request: http://localhost:8080/student/param?version=2 → Response: {"name":{"firstName":"javadaily","lastName":"JAVA日知录"}}
Custom‑header versioning
@RestController
public class StudentHeaderController {
@GetMapping(value="/student/header", headers = "X-API-VERSION=1")
public StudentV1 studentV1() {
return new StudentV1("javadaily");
}
@GetMapping(value="/student/header", headers = "X-API-VERSION=2")
public StudentV2 studentV2() {
return new StudentV2(new Name("javadaily", "JAVA日知录"));
}
}Send a GET request to http://localhost:8080/student/header with header X-API-VERSION: 1 to get version 1, or with X-API-VERSION: 2 to get version 2.
Media‑type (Accept header) versioning
@RestController
public class StudentProduceController {
@GetMapping(value="/student/produce", produces = "application/api-v1+json")
public StudentV1 studentV1() {
return new StudentV1("javadaily");
}
@GetMapping(value="/student/produce", produces = "application/api-v2+json")
public StudentV2 studentV2() {
return new StudentV2(new Name("javadaily", "JAVA日知录"));
}
}Send a GET request to http://localhost:8080/student/produce with Accept: application/api-v1+json for version 1, or Accept: application/api-v2+json for version 2.
Factors influencing versioning choice
URI pollution – URL‑based versioning and request‑parameter versioning pollute the URI space.
Misuse of request headers – the Accept header was not designed for versioning.
Caching – header‑based versioning requires cache handling for specific headers.
Browser accessibility – URL‑based versions are easier for non‑technical consumers.
API documentation – how to make documentation reflect that two URLs represent the same service.
In fact, there is no perfect versioning solution; you need to choose based on the project's actual situation.
Major API providers and the versioning methods they use:
Media‑type versioning – GitHub
Custom header – Microsoft
URI path – Twitter, Baidu, Zhihu
Request‑parameter – Amazon
That's all for today's article; hope it helps you.
— PiaoMiao Jam, architect and developer
Recommended reading:
How to use Binlog for cross‑system data synchronization
High‑Concurrency Service Optimization: Detailed RPC Call Process
How to design a high‑performance flash‑sale system
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.