Declarative HTTP Interfaces, introduced in Spring Framework 6, are a strong feature that streamlines API interactions by removing boilerplate code. Instead of manually using WebClient in services, you may now define HTTP operations using annotated interfaces, making code easier to maintain and test.
In this blog post, we will examine how to use Declarative HTTP Interfaces in Spring 6 using a real-world example.
Declarative HTTP interfaces allow you to define HTTP clients by annotating Java interfaces with @HttpExchange. Spring automatically implements these interfaces with WebClient. This method is similar to Feign clients in Spring Cloud, but it is now a built-in part of Spring Framework 6.
Let's look at a real-world example of consuming a REST API using Spring 6's declarative approach.
Dependencies Used
To use WebClient and declarative HTTP interfaces, ensure you have the following dependencies in your pom.xml:
<dependencies> <!-- Spring WebFlux for reactive WebClient --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- Lombok for reducing boilerplate code --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Spring Boot Starter for Spring applications --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
1. Define the HTTP Client Interface
We create the PostClient interface to interact with a JSONPlaceholder API that generates dummy posts.
package com.example.demo.client; import com.example.demo.model.Post; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; import java.util.List; @HttpExchange(accept = "application/json", url = "/posts") public interface PostClient { @GetExchange List<Post> getAllPost(); @GetExchange("/{postId}") Post getPostById(@PathVariable("postId") int postId); }
In this interface:
2. Configure WebClient and Register the HTTP Client
We now configure WebClient and register PostClient as a bean.
package com.example.demo.config; import com.example.demo.client.PostClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.web.service.invoker.WebClientAdapter; @Configuration public class PostConfig { @Bean WebClient webClient() { return WebClient.builder() .baseUrl("https://jsonplaceholder.typicode.com") .build(); } @Bean public PostClient postClient() { WebClientAdapter webClientAdapter = WebClientAdapter.create(webClient()); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(webClientAdapter).build(); return factory.createClient(PostClient.class); } }
Here’s what’s happening:
3. Implement a Service Layer
Now, let’s create a service class PostService to use PostClient for fetching posts.
package com.example.demo.service; import com.example.demo.client.PostClient; import com.example.demo.model.Post; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; @Service @RequiredArgsConstructor public class PostService { private final PostClient postClient; public List<Post> getPosts() { return postClient.getAllPost(); } public Post getPostById(int id) { return postClient.getPostById(id); } }
4. Expose an API Endpoint
We need a controller to expose these services via a REST API.
package com.example.demo.controller; import com.example.demo.model.Post; import com.example.demo.service.PostService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("api/v1/post") @RequiredArgsConstructor public class PostController { private final PostService postService; @GetMapping public List<Post> getPosts() { return postService.getPosts(); } @GetMapping("/{id}") public Post getPostById(@PathVariable int id) { return postService.getPostById(id); } }
5. Define the Post Model
Finally, we define a Post model to map the JSON response.
package com.example.demo.model; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Post { private Integer id; private String title; private String body; private Integer userId; }
Once the application is running, you can test the API:
If you encounter a certificate error, you can work around it by modifying the WebClient bean.
WebClient webClient() throws Exception { // Create SSL context that accepts all certificates SslContext sslContext = SslContextBuilder .forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build(); // Create HttpClient with the SSL context HttpClient httpClient = HttpClient.create() .secure(t -> t.sslContext(sslContext)); // Create WebClient with the configured HttpClient return WebClient.builder() .baseUrl("https://jsonplaceholder.typicode.com") .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); }
Spring 6’s Declarative HTTP Interfaces simplify API communication with a clean, interface-driven approach. By using @HttpExchange with WebClient, you can efficiently consume REST APIs with minimal code.
Try implementing this feature in your Spring projects and experience the benefits firsthand!
At DevelopersMonk, we share tutorials, tips, and insights on modern programming frameworks like React, Next.js, Spring Boot, and more. Join us on our journey to simplify coding and empower developers worldwide!