package com.example.demo.controller;
import com.example.demo.dto.UserDTO;
import com.example.demo.dto.v2.UserDTOV2;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
private final UserService userService;
public UserControllerV1(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<UserDTO> getUsers() {
return userService.getAllUsersV1();
}
@GetMapping("/{id}")
public UserDTO getUser(@PathVariable Long id) {
return userService.getUserV1(id);
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
private final UserService userService;
public UserControllerV2(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<UserDTOV2> getUsers() {
return userService.getAllUsersV2();
}
@GetMapping("/{id}")
public UserDTOV2 getUser(@PathVariable Long id) {
return userService.getUserV2(id);
}
// New endpoint in V2
@GetMapping("/{id}/profile")
public UserProfile getUserProfile(@PathVariable Long id) {
return userService.getUserProfile(id);
}
}
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class VersionedUserController {
private final UserService userService;
public VersionedUserController(UserService userService) {
this.userService = userService;
}
@GetMapping(headers = "X-API-Version=1")
public List<UserDTO> getUsersV1() {
return userService.getAllUsersV1();
}
@GetMapping(headers = "X-API-Version=2")
public List<UserDTOV2> getUsersV2() {
return userService.getAllUsersV2();
}
}
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class ContentNegotiationController {
private final UserService userService;
public ContentNegotiationController(UserService userService) {
this.userService = userService;
}
@GetMapping(produces = "application/vnd.myapi.v1+json")
public List<UserDTO> getUsersV1() {
return userService.getAllUsersV1();
}
@GetMapping(produces = "application/vnd.myapi.v2+json")
public List<UserDTOV2> getUsersV2() {
return userService.getAllUsersV2();
}
}
package com.example.demo.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class ApiVersionInterceptor implements HandlerInterceptor {
private static final String VERSION_HEADER = "X-API-Version";
private static final String DEFAULT_VERSION = "1";
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
) {
String version = request.getHeader(VERSION_HEADER);
if (version == null || version.isEmpty()) {
request.setAttribute("api.version", DEFAULT_VERSION);
} else {
request.setAttribute("api.version", version);
}
// Add deprecation warning for old versions
if ("1".equals(version)) {
response.addHeader("X-API-Warn",
"API version 1 is deprecated. Please migrate to version 2.");
}
return true;
}
}
API versioning manages evolution while supporting existing clients. URI versioning uses paths—/api/v1/users, /api/v2/users. Header versioning employs custom headers—X-API-Version: 2. Content negotiation uses Accept headers—application/vnd.myapi.v2+json. Query parameter versioning—/api/users?version=2. I prefer URI versioning for simplicity and visibility. Versioning enables breaking changes without disrupting clients. Deprecation warnings guide migrations. Version-specific controllers or routing handle differences. Shared logic avoids duplication. Documentation clearly indicates version differences. Proper versioning strategy balances backward compatibility with forward progress. It's essential for public APIs and long-lived applications.
Related snips
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
package com.example.starter.config;
import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Custom Spring Boot starters
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
"""Allow only object owner to access."""
Django REST Framework permissions and authorization
package com.example.demo.config;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
Messaging with Apache Kafka
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts do
resources :comments, only: [:index, :create]
end
Rails API versioning strategies
package com.example.demo.controller;
import com.example.demo.dto.FileMetadata;
import com.example.demo.service.FileStorageService;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
File upload and download handling
Share this code
Here's the card — post it anywhere.