A demonstration project showcasing the new HTTP Service Client features in Spring Framework 7 and Spring Boot 4. This project uses declarative HTTP interfaces to consume the JSONPlaceholder API.
HTTP Interfaces allow you to define an HTTP service as a Java interface with annotated methods for HTTP exchanges. Spring Framework creates a proxy that implements the interface and performs the HTTP requests. This provides a type-safe, declarative approach to building HTTP clients.
@HttpExchange("/todos")
public interface TodoService {
@GetExchange
List<Todo> findAll();
@GetExchange("/{id}")
Todo findById(@PathVariable Integer id);
@PostExchange
Todo create(@RequestBody Todo todo);
}- Declarative HTTP Clients - Define HTTP services using Java interfaces
- Type-Safe - Full compile-time type checking for requests and responses
- Annotation-Based - Use
@HttpExchange,@GetExchange,@PostExchange,@PutExchange,@DeleteExchange - RestClient Integration - Built on Spring's modern RestClient
- Spring Boot Auto-Configuration - Minimal configuration required
- Java 25
- Spring Boot 4.0.0+
- Spring Framework 7.0
- Maven 3.9+
src/main/java/dev/danvega/http/
├── Application.java # Spring Boot entry point
├── ClientConfig.java # HTTP client configuration
├── todo/
│ ├── Todo.java # Todo record model
│ ├── TodoService.java # HTTP interface for JSONPlaceholder /todos
│ └── TodoController.java # REST controller exposing /api/todos
└── post/
├── Post.java # Post record model
├── PostService.java # HTTP interface for JSONPlaceholder /posts
└── PostController.java # REST controller exposing /api/posts
src/main/resources/
├── application.yaml # Application configuration
├── todo.http # HTTP client tests for todos
└── post.http # HTTP client tests for posts
git clone <repository-url>
cd sb4-http-interfaces./mvnw clean install./mvnw spring-boot:runThe application will start on http://localhost:8080.
Use Java records for immutable data transfer objects:
public record Todo(Integer id, Integer userId, String title, boolean completed) {
}Define methods for each HTTP operation using exchange annotations:
@HttpExchange("/todos")
public interface TodoService {
@GetExchange
List<Todo> findAll();
@GetExchange("/{id}")
Todo findById(@PathVariable Integer id);
@PostExchange
Todo create(@RequestBody Todo todo);
@PutExchange("/{id}")
Todo update(@PathVariable Integer id, @RequestBody Todo todo);
@DeleteExchange("/{id}")
void delete(@PathVariable Integer id);
}Create a configuration class to set up RestClient and the proxy factory:
@Configuration
public class ClientConfig {
@Bean
RestClient jsonplaceholderRestClient() {
return RestClient.builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.build();
}
@Bean
HttpServiceProxyFactory jsonPlaceholderProxyFactory(RestClient jsonplaceholderRestClient) {
return HttpServiceProxyFactory.builder()
.exchangeAdapter(RestClientAdapter.create(jsonplaceholderRestClient))
.build();
}
@Bean
TodoService todoService(HttpServiceProxyFactory jsonPlaceholderProxyFactory) {
return jsonPlaceholderProxyFactory.createClient(TodoService.class);
}
}Expose the HTTP interface through your own REST API:
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService todoService;
public TodoController(TodoService todoService) {
this.todoService = todoService;
}
@GetMapping
public List<Todo> findAll() {
return todoService.findAll();
}
@GetMapping("/{id}")
public Todo findById(@PathVariable Integer id) {
return todoService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Todo create(@RequestBody Todo todo) {
return todoService.create(todo);
}
@PutMapping("/{id}")
public Todo update(@PathVariable Integer id, @RequestBody Todo todo) {
return todoService.update(id, todo);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Integer id) {
todoService.delete(id);
}
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/todos |
Get all todos |
| GET | /api/todos/{id} |
Get todo by ID |
| POST | /api/todos |
Create a new todo |
| PUT | /api/todos/{id} |
Update an existing todo |
| DELETE | /api/todos/{id} |
Delete a todo |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/posts |
Get all posts |
| GET | /api/posts/{id} |
Get post by ID |
| POST | /api/posts |
Create a new post |
| PUT | /api/posts/{id} |
Update an existing post |
| DELETE | /api/posts/{id} |
Delete a post |
The project includes .http files for testing endpoints using IntelliJ IDEA or VS Code (with REST Client extension).
### Get all todos
GET http://localhost:8080/api/todos
Accept: application/json
### Get todo by ID
GET http://localhost:8080/api/todos/1
Accept: application/json
### Create a new todo
POST http://localhost:8080/api/todos
Content-Type: application/json
{
"userId": 1,
"title": "Learn Spring Boot HTTP Interfaces",
"completed": false
}This project demonstrates the HTTP interface feature that was introduced in Spring Framework 6 and enhanced in Spring Framework 7. Key improvements include:
Spring Framework 7 introduces a registry layer over HttpServiceProxyFactory that provides:
- Configuration model to register HTTP interfaces and initialize HTTP client infrastructure
- Transparent creation and registration of client proxies as Spring beans
- Access to all client proxies via
HttpServiceProxyRegistry
Spring Framework 7 introduces @ImportHttpServices for declaratively registering HTTP service groups, reducing boilerplate configuration.