CODE WITH SIBIN

Solving Real Problems with Real Code


Spring Boot RestClient Tutorial: Setup, Authentication, Error Handling & Advanced Usage

Table of Contents

  1. Introduction to RestClient
  2. Setting Up RestClient in Spring Boot
  3. Making HTTP Requests with RestClient
  4. Handling Responses in RestClient
  5. Customizing RestClient
  6. Authentication with RestClient
  7. Advanced Usage of RestClient
  8. Testing with RestClient

    1. Introduction to RestClient

    In Spring Boot 3.2, a new HTTP client called RestClient was released as a RestClient is a new HTTP client introduced in Spring Boot 3.2 as a modern alternative for RestTemplate. By offering a more declarative and fluent API while maintaining synchronous behavior, it makes submitting HTTP requests easier.

    It brings a balance between RestTemplate and WebClient and is simple to use, test, and configure because it is built on top of HttpInterface.

    RestClient is a new HTTP client introduced in Spring Boot 3.2 as a modern alternative to RestTemplate. It simplifies making HTTP requests by providing a more fluent and declarative API while retaining synchronous behavior.

    It is built on top of HttpInterface, which makes it easy to use, test, and configure, bringing a balance between RestTemplate and WebClient.

    Why Use RestClient Over RestTemplate/WebClient?

    FeatureRestClientRestTemplateWebClient
    Introduced InSpring Boot 3.2Spring 3.0Spring WebFlux
    API StyleFluent & DeclarativeVerbose & ImperativeReactive
    Blocking/Synchronous✅ Yes✅ Yes❌ No (Non-blocking)
    Async Support❌ No❌ No✅ Yes
    Streaming Support✅ Yes❌ No✅ Yes
    Ease of Use✅ Simple & Modern❌ Verbose⚠️ Complex for Simple Use Cases
    Recommended ForModern synchronous applicationsLegacy supportReactive applications

    Why use RestClient instead of RestTemplate?

    • RestTemplate is now in maintenance mode and will be deprecated in the future.
    • RestClient provides a fluent API that improves readability and usability.
    • It allows better integration with error handling, authentication, and interceptors.

    Why use RestClient instead of WebClient?

    • RestClient is synchronous, making it easier to use in traditional Spring MVC applications.
    • WebClient is asynchronous and non-blocking, which is better suited for reactive applications.
    • If you don’t need reactive programming, RestClient provides better simplicity and ease of use.

    Features & Benefits of RestClient

    ☑️ Fluent & Readable API – Allows writing concise, readable, and chainable request calls.
    ☑️ Supports Serialization/Deserialization – Automatically maps JSON responses to Java objects.
    ☑️ Error Handling Built-in – Supports custom exception handling for different HTTP status codes.
    ☑️ Easier Configuration – Configurable with authentication, timeouts, and interceptors.
    ☑️ Supports Streaming – Handles large responses efficiently.
    ☑️ Modern and Future-Proof – Designed for newer Spring Boot versions with better maintainability

    2. Setting Up RestClient in Spring Boot

    To use RestClient in a Spring Boot 3.2+ application, you need to set up the necessary dependencies and configure a RestClient bean. This section covers both steps in detail.

    Adding Dependencies (Spring Boot 3.2+ Required)

    Starting from Spring Boot 3.2, RestClient is included in the spring-boot-starter-web and spring-boot-starter-webflux dependencies.

    Maven Dependency

    If your project already has spring-boot-starter-web, you don’t need to add anything extra.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    If you're using Spring WebFlux, RestClient is also included:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    Configuring RestClient Bean

    To use RestClient throughout your application, you can create a global RestClient bean in a configuration class.

    1. Basic RestClient Bean Configuration

    The simplest way to configure RestClient is by creating a bean using RestClient.create().

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestClient;
    
    @Configuration
    public class RestClientConfig {
    
        @Bean
        public RestClient restClient() {
            return RestClient.create(); // Creates a default RestClient instance
        }
    }
    • This RestClient bean can now be injected and used across the application.
    • It uses default settings and does not have a predefined base URL.

    2. Configuring RestClient with a Base URL

    If you frequently call the same API, it's better to configure a base URL in the bean.

    @Bean
    public RestClient restClient() {
        return RestClient.builder()
                .baseUrl("https://jsonplaceholder.typicode.com") // Base URL for all requests
                .build();
    }
    • Now, all requests made using this RestClient will use https://jsonplaceholder.typicode.com as the base.

    3. Customizing RestClient with Headers, Timeouts, and Interceptors

    You can configure RestClient with default headers, timeouts, and interceptors to customize its behavior.

    import java.time.Duration;
    import org.springframework.http.client.JdkClientHttpRequestFactory;
    
    @Bean
    public RestClient restClient() {
        return RestClient.builder()
                .baseUrl("https://api.example.com")
                .defaultHeader("Authorization", "Bearer your_token") // Set Authorization header
                .defaultHeader("Content-Type", "application/json") // Set default Content-Type
                .requestFactory(new JdkClientHttpRequestFactory()) // Use a custom HTTP request factory
                .build();
    }

    Key Customizations:

    ☑️ Base URL – Defines a global API URL for requests.
    ☑️ Headers – Automatically adds default headers (e.g., Authorization).
    ☑️ Timeouts – You can set timeout values using a request factory.
    ☑️ Interceptors – Useful for logging, modifying requests, or handling authentication.

    Injecting and Using RestClient in a Service

    Once you’ve configured the RestClient bean, you can inject it into your services and use it to make HTTP calls.

    Example: Using RestClient for a GET Request

    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestClient;
    
    @Service
    public class ApiService {
    
        private final RestClient restClient;
    
        public ApiService(RestClient restClient) {
            this.restClient = restClient;
        }
    
        public String getData() {
            return restClient.get()
                    .uri("/posts/1") // This will use the base URL from the config
                    .retrieve()
                    .body(String.class);
        }
    }

    What Happens Here?

    1. RestClient is injected into the ApiService class.
    2. The getData() method sends a GET request to https://jsonplaceholder.typicode.com/posts/1.
    3. The response is automatically converted into a Java String (can be replaced with a DTO).

    3. Making HTTP Requests with RestClient

    Once RestClient is configured, you can use it to make GET, POST, PUT, and DELETE HTTP requests. This section covers how to perform these operations with examples.

    1. Making GET Requests

    A GET request is used to fetch data from an API.

    Example: Simple GET Request

    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestClient;
    
    @Service
    public class ApiService {
    
        private final RestClient restClient;
    
        public ApiService(RestClient restClient) {
            this.restClient = restClient;
        }
    
        public String getPostById(int id) {
            return restClient.get()
                    .uri("/posts/{id}", id) // Uses path variable
                    .retrieve()
                    .body(String.class); // Returns response body as a String
        }
    }

    Key Points:

    ☑️ restClient.get() starts a GET request.
    ☑️ .uri("/posts/{id}", id) dynamically replaces {id} with the provided value.
    ☑️ .retrieve().body(String.class) extracts the response body.

    2. Making POST Requests

    A POST request is used to send data to a server to create a new resource.

    Example: Sending a JSON Request Body

    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestClient;
    
    @Service
    public class ApiService {
    
        private final RestClient restClient;
    
        public ApiService(RestClient restClient) {
            this.restClient = restClient;
        }
    
        public String createPost(Post post) {
            return restClient.post()
                    .uri("/posts")
                    .body(post) // Sends the request body as JSON
                    .retrieve()
                    .body(String.class); // Returns the created resource
        }
    }

    Post Model (DTO)

    public class Post {
        private String title;
        private String body;
        private int userId;
    
        // Constructors, Getters, Setters
    }

    Key Points:

    ☑️ restClient.post() starts a POST request.
    ☑️ .body(post) automatically serializes the Java object into JSON.
    ☑️ The API will return the created resource as a response.

    3. Making PUT Requests

    A PUT request updates an existing resource.

    Example: Updating a Post

    public String updatePost(int id, Post post) {
        return restClient.put()
                .uri("/posts/{id}", id)
                .body(post)
                .retrieve()
                .body(String.class);
    }

    Key Points:

    ☑️ restClient.put() starts a PUT request.
    ☑️ .body(post) sends the updated data as JSON.
    ☑️ The API updates the resource and returns the modified version.

    4. Making DELETE Requests

    A DELETE request removes a resource from the server.

    Example: Deleting a Post

    public void deletePost(int id) {
        restClient.delete()
                .uri("/posts/{id}", id)
                .retrieve();
    }

    Key Points:

    ☑️ restClient.delete() starts a DELETE request.
    ☑️ .uri("/posts/{id}", id) specifies which resource to delete.
    ☑️ No response body is needed, so .body(String.class) is omitted.

    5. Handling Response Objects

    Instead of using String.class, you can deserialize JSON responses into Java objects.

    Example: Using a DTO for GET Request

    public Post getPostAsObject(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .retrieve()
                .body(Post.class); // Automatically maps JSON to Post object
    }
    • This returns a Post object instead of raw JSON.
    • Spring automatically maps the API response to the Post class.

    4. Handling Responses in RestClient

    When making HTTP requests with RestClient, handling responses properly is crucial. This section covers:

    1. Deserializing JSON responses into Java objects.
    2. Handling different response types like List<T> and single objects.
    3. Error handling and exception mapping for robust API communication.

    1. Deserializing JSON Responses

    RestClient can automatically deserialize JSON responses into Java objects.

    Example: Mapping JSON to a Java Object

    Assume an API returns the following JSON:

    {
      "id": 1,
      "title": "Spring Boot RestClient",
      "body": "RestClient makes HTTP calls easier",
      "userId": 100
    }

    Java DTO (Data Transfer Object) to Map JSON Response

    public class Post {
        private int id;
        private String title;
        private String body;
        private int userId;
    
        // Getters and setters
    }

    Using RestClient to Deserialize JSON to Post Object

    public Post getPost(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .retrieve()
                .body(Post.class); // Converts JSON to Post object
    }

    ☑️ Spring automatically maps JSON fields to the Java class properties using Jackson.

    2. Handling Different Response Types

    a) Handling a List of Objects (Multiple Items in Response)

    If an API returns an array of objects, use List<T>.

    Example API Response (JSON Array):

    [
      {
        "id": 1,
        "title": "Post 1",
        "body": "Body of post 1",
        "userId": 100
      },
      {
        "id": 2,
        "title": "Post 2",
        "body": "Body of post 2",
        "userId": 101
      }
    ]

    Handling List Response in Java:

    import java.util.List;
    import org.springframework.core.ParameterizedTypeReference;
    
    public List<Post> getAllPosts() {
        return restClient.get()
                .uri("/posts")
                .retrieve()
                .body(new ParameterizedTypeReference<List<Post>>() {});
    }

    ☑️ Use ParameterizedTypeReference<List<Post>> to handle generics properly.

    b) Handling Different Response Types

    Response TypeUsage
    Single Object (T)restClient.get().body(Post.class)
    List of Objects (List<T>)restClient.get().body(new ParameterizedTypeReference<List<Post>>() {})
    Raw JSON (String)restClient.get().body(String.class)
    Custom Response Wrapper (ResponseEntity<T>)restClient.get().body(ResponseEntity.class)

    3. Error Handling and Exception Mapping

    When API calls fail (e.g., 404 Not Found, 500 Server Error, or timeouts), RestClient can handle errors gracefully.

    Basic Error Handling (Using onStatus)

    public Post getPostWithErrorHandling(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .retrieve()
                .onStatus(HttpStatusCode::isError, (request, response) -> {
                    throw new RuntimeException("Failed to fetch post. Status: " + response.getStatusCode());
                })
                .body(Post.class);
    }

    ☑️ onStatus(HttpStatusCode::isError, handler) detects HTTP errors and throws custom exceptions.

    Advanced Exception Mapping (Custom Exception Handling)

    Instead of throwing a generic RuntimeException, create custom exceptions based on the HTTP status code.

    Custom Exception Classes

    public class NotFoundException extends RuntimeException {
        public NotFoundException(String message) {
            super(message);
        }
    }
    
    public class ApiException extends RuntimeException {
        public ApiException(String message) {
            super(message);
        }
    }

    Handling Specific HTTP Errors

    import org.springframework.http.HttpStatusCode;
    
    public Post getPostWithCustomErrorHandling(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .retrieve()
                .onStatus(status -> status.value() == 404, (request, response) -> {
                    throw new NotFoundException("Post not found!");
                })
                .onStatus(HttpStatusCode::isError, (request, response) -> {
                    throw new ApiException("API Error: " + response.getStatusCode());
                })
                .body(Post.class);
    }

    ☑️ Throws NotFoundException for 404 errors and ApiException for other HTTP errors.

    4. Handling Empty Responses (204 No Content)

    Some APIs return 204 No Content when there is no response body. Handle this properly.

    Example: Handling Empty Response Gracefully

    public Optional<Post> getPostOptional(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .retrieve()
                .toEntity(Post.class)
                .map(response -> response.getBody() != null ? Optional.of(response.getBody()) : Optional.empty());
    }

    ☑️ Wraps the response in Optional<T> to avoid NullPointerException.

    5. Customizing RestClient

    To enhance API communication with RestClient, customization is key. This section covers:

    ☑️ Setting custom headers (e.g., authentication, content type)
    ☑️ Configuring timeouts (preventing slow requests from blocking your app)
    ☑️ Using interceptors for logging requests and responses

    1. Setting Custom Headers

    Custom headers are often needed for authentication (e.g., Authorization header) or content negotiation (e.g., Accept header).

    Example: Adding Headers Dynamically

    public Post getPostWithHeaders(int id) {
        return restClient.get()
                .uri("/posts/{id}", id)
                .header("Authorization", "Bearer my-token")  // Adds a custom header
                .header("Accept", "application/json")        // Specifies response format
                .retrieve()
                .body(Post.class);
    }

    ☑️ .header(key, value) allows adding headers dynamically per request.

    Example: Using Headers for a POST Request

    public String createPostWithHeaders(Post post) {
        return restClient.post()
                .uri("/posts")
                .header("Authorization", "Bearer my-token")
                .header("Content-Type", "application/json")
                .body(post)
                .retrieve()
                .body(String.class);
    }

    ☑️ For POST requests, Content-Type is essential when sending JSON data.

    2. Configuring Timeouts

    Time-sensitive APIs need timeouts to prevent slow responses from blocking your application.

    Setting Timeouts in a RestClient Bean

    import java.time.Duration;
    import org.springframework.boot.web.client.RestClientCustomizer;
    import org.springframework.stereotype.Component;
    
    @Component
    public class RestClientTimeoutCustomizer implements RestClientCustomizer {
        @Override
        public void customize(RestClient.Builder builder) {
            builder.requestFactory(factory -> factory.setConnectTimeout(Duration.ofSeconds(5))  // Connection timeout
                                                 .setReadTimeout(Duration.ofSeconds(10)));     // Read timeout
        }
    }

    ☑️ Connect timeout: Max time to establish a connection.
    ☑️ Read timeout: Max time to wait for a server response.

    Using Timeouts in an Inline Request (Without Bean Customization)

    import java.time.Duration;
    
    public Post getPostWithTimeout(int id) {
        return RestClient.builder()
                .requestFactory(factory -> factory.setConnectTimeout(Duration.ofSeconds(3))
                                                  .setReadTimeout(Duration.ofSeconds(8)))
                .build()
                .get()
                .uri("/posts/{id}", id)
                .retrieve()
                .body(Post.class);
    }

    ☑️ This inline approach is useful for temporary changes in timeout settings.

    3. Interceptors and Logging Requests

    Interceptors allow you to:
    🔹 Log request and response data for debugging.
    🔹 Add authentication tokens dynamically.
    🔹 Modify or inspect requests before sending them.

    Example: Custom Logging Interceptor

    import org.springframework.http.HttpRequest;
    import org.springframework.http.client.ClientHttpRequestExecution;
    import org.springframework.http.client.ClientHttpRequestInterceptor;
    import org.springframework.http.client.ClientHttpResponse;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    public class LoggingInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            System.out.println("Request URI: " + request.getURI());
            System.out.println("Request Method: " + request.getMethod());
            System.out.println("Request Headers: " + request.getHeaders());
            System.out.println("Request Body: " + new String(body, StandardCharsets.UTF_8));
    
            ClientHttpResponse response = execution.execute(request, body);
            System.out.println("Response Status: " + response.getStatusCode());
            return response;
        }
    }

    ☑️ Logs request details (URI, headers, body) and response status.

    Registering the Interceptor in RestClient

    import org.springframework.stereotype.Component;
    import org.springframework.boot.web.client.RestClientCustomizer;
    
    @Component
    public class RestClientInterceptorCustomizer implements RestClientCustomizer {
        @Override
        public void customize(RestClient.Builder builder) {
            builder.requestInterceptor(new LoggingInterceptor()); // Adds custom logging
        }
    }

    ☑️ Spring Boot will automatically apply this interceptor to all RestClient requests.

    4. Customizing RestClient with Authentication

    Many APIs require authentication via Bearer Tokens or Basic Auth.

    Example: Adding Bearer Token to Every Request

    import org.springframework.stereotype.Component;
    import org.springframework.boot.web.client.RestClientCustomizer;
    
    @Component
    public class RestClientAuthCustomizer implements RestClientCustomizer {
        @Override
        public void customize(RestClient.Builder builder) {
            builder.defaultHeader("Authorization", "Bearer my-secret-token"); // Applies to all requests
        }
    }

    ☑️ Now every request automatically includes the Authorization header.

    6. Authentication with RestClient

    Authentication is crucial when working with external APIs. RestClient supports multiple authentication methods, including:

    ☑️ Basic Authentication (username/password in headers)
    ☑️ Bearer Token Authentication (commonly used for APIs with JWT or OAuth2)
    ☑️ OAuth2 Integration (for secure, delegated access)

    1. Basic Authentication

    Basic Authentication requires a username and password sent in the Authorization header as a Base64-encoded string (username:password).

    Example: Manually Adding Basic Auth Header

    import java.util.Base64;
    
    public Post getPostWithBasicAuth(int id) {
        String username = "admin";
        String password = "password";
        String authHeader = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
    
        return restClient.get()
                .uri("/posts/{id}", id)
                .header("Authorization", authHeader)  // Adds the Basic Auth header
                .retrieve()
                .body(Post.class);
    }

    ☑️ Manually encodes username & password as Base64 for authentication.

    Better Approach: Using defaultHeader for Global Authentication

    Instead of setting headers in every request, configure them globally:

    import org.springframework.stereotype.Component;
    import org.springframework.boot.web.client.RestClientCustomizer;
    import java.util.Base64;
    
    @Component
    public class RestClientBasicAuthCustomizer implements RestClientCustomizer {
        @Override
        public void customize(RestClient.Builder builder) {
            String username = "admin";
            String password = "password";
            String authHeader = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
    
            builder.defaultHeader("Authorization", authHeader); // Applies Basic Auth to all requests
        }
    }

    ☑️ Now, all requests include Basic Authentication automatically.

    2. Bearer Token Authentication

    Many APIs use Bearer Tokens (e.g., JWTs or API tokens) for authentication. The token is sent in the Authorization header.

    Example: Manually Adding a Bearer Token

    public Post getPostWithBearerToken(int id) {
        String token = "your-jwt-token";
    
        return restClient.get()
                .uri("/posts/{id}", id)
                .header("Authorization", "Bearer " + token)  // Adds Bearer token
                .retrieve()
                .body(Post.class);
    }

    ☑️ Manually sets the token in each request.

    Better Approach: Global Bearer Token Configuration

    Instead of setting the token manually, apply it globally to all requests:

    import org.springframework.stereotype.Component;
    import org.springframework.boot.web.client.RestClientCustomizer;
    
    @Component
    public class RestClientBearerTokenCustomizer implements RestClientCustomizer {
        @Override
        public void customize(RestClient.Builder builder) {
            String token = "your-jwt-token"; // Retrieve from a config or auth service
    
            builder.defaultHeader("Authorization", "Bearer " + token); // Applies token globally
        }
    }

    ☑️ Now all RestClient requests include the Bearer Token.

    3. OAuth2 Integration with RestClient

    OAuth2 authentication allows secure access to APIs without exposing credentials. Spring Boot provides built-in OAuth2 support via spring-security-oauth2-client.

    Step 1: Add Dependencies

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>

    ☑️ This enables Spring Boot’s OAuth2 client support.

    Step 2: Configure OAuth2 in application.yml

    spring:
      security:
        oauth2:
          client:
            registration:
              my-oauth-client:
                client-id: your-client-id
                client-secret: your-client-secret
                authorization-grant-type: client_credentials
                provider: my-oauth-provider
            provider:
              my-oauth-provider:
                token-uri: https://auth-server.com/oauth/token

    ☑️ Defines OAuth2 client details and token URL.

    Step 3: Inject OAuth2AuthorizedClientService to Get Tokens

    import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
    import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OAuth2TokenService {
        private final OAuth2AuthorizedClientService authorizedClientService;
    
        public OAuth2TokenService(OAuth2AuthorizedClientService authorizedClientService) {
            this.authorizedClientService = authorizedClientService;
        }
    
        public String getAccessToken(OAuth2AuthenticationToken authentication) {
            OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
                    authentication.getAuthorizedClientRegistrationId(), authentication.getName());
    
            return client.getAccessToken().getTokenValue();
        }
    }

    ☑️ Retrieves an OAuth2 token dynamically.

    Step 4: Use OAuth2 Token in RestClient

    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ApiService {
        private final RestClient restClient;
        private final OAuth2TokenService tokenService;
    
        public ApiService(RestClient restClient, OAuth2TokenService tokenService) {
            this.restClient = restClient;
            this.tokenService = tokenService;
        }
    
        public Post getPostWithOAuth2(int id, OAuth2AuthenticationToken authentication) {
            String token = tokenService.getAccessToken(authentication);
    
            return restClient.get()
                    .uri("/posts/{id}", id)
                    .header("Authorization", "Bearer " + token)
                    .retrieve()
                    .body(Post.class);
        }
    }

    ☑️ Dynamically fetches and applies OAuth2 tokens for API calls.

    7. Advanced Usage of RestClient

    Beyond basic HTTP requests, RestClient supports advanced use cases such as:

    ☑️ Streaming Large Responses (handling large payloads efficiently)
    ☑️ Multipart/Form-Data Requests (for file uploads and complex forms)

    1. Streaming Large Responses

    APIs may return large JSON/XML files, causing memory issues if loaded entirely at once. Streaming allows processing data incrementally.

    🔹 Using RestClient with InputStream

    import java.io.InputStream;
    import java.util.Scanner;
    
    public void streamLargeResponse() {
        try (InputStream responseStream = restClient.get()
                .uri("/large-data")
                .retrieve()
                .body(InputStream.class);
             Scanner scanner = new Scanner(responseStream)) {
    
            while (scanner.hasNextLine()) {
                System.out.println(scanner.nextLine());  // Process data line by line
            }
        }
    }

    ☑️ Processes large responses without loading everything into memory.

    🔹 Using WebClient for Non-Blocking Streaming

    For truly non-blocking streaming, use WebClient:

    import reactor.core.publisher.Flux;
    
    public void streamLargeResponseAsync() {
        Flux<String> responseFlux = webClient.get()
                .uri("/large-data")
                .retrieve()
                .bodyToFlux(String.class);
    
        responseFlux.subscribe(System.out::println);  // Processes data asynchronously
    }

    ☑️ Ideal for large datasets that need real-time processing.

    2. Working with Multipart/Form-Data Requests

    Some APIs require multipart requests to upload files or forms. RestClient supports this with MultipartBodyBuilder.

    🔹 Example: Uploading a File with RestClient

    import org.springframework.http.MediaType;
    import org.springframework.core.io.FileSystemResource;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    
    public void uploadFile() {
        FileSystemResource file = new FileSystemResource("path/to/file.jpg");
    
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", file);
    
        String response = restClient.post()
                .uri("/upload")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(body)
                .retrieve()
                .body(String.class);
    
        System.out.println("Upload response: " + response);
    }

    ☑️ Sends a multipart request with a file.

    🔹 Example: Uploading Multiple Files and Form Data

    import org.springframework.core.io.FileSystemResource;
    import org.springframework.util.MultiValueMap;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.http.MediaType;
    
    public void uploadMultipleFiles() {
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file1", new FileSystemResource("file1.jpg"));
        body.add("file2", new FileSystemResource("file2.png"));
        body.add("description", "Uploading multiple files");
    
        String response = restClient.post()
                .uri("/upload-multiple")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(body)
                .retrieve()
                .body(String.class);
    
        System.out.println("Upload response: " + response);
    }

    ☑️ Uploads multiple files along with form data.

    8. Testing with RestClient

    Testing API interactions is essential to ensure reliability, correctness, and error handling in applications that use RestClient. This section covers:

    ☑️ Mocking HTTP calls using MockRestServiceServer
    ☑️ Unit Testing (isolating API calls)
    ☑️ Integration Testing (testing API calls in a real Spring Boot environment)

    1. Mocking HTTP Calls Using MockRestServiceServer

    When writing unit tests, you don’t want to make real API calls. Instead, you can mock API responses using MockRestServiceServer.

    🔹 Step 1: Dependency

    Ensure you have spring-boot-starter-test in your pom.xml:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    ☑️ Includes JUnit, Mockito, and Spring Test utilities for mocking API calls.

    🔹 Step 2: Create a RestClient Service for Testing

    import org.springframework.web.client.RestClient;
    import org.springframework.stereotype.Service;
    
    @Service
    public class PostService {
        private final RestClient restClient;
    
        public PostService(RestClient.Builder builder) {
            this.restClient = builder.baseUrl("https://jsonplaceholder.typicode.com").build();
        }
    
        public Post getPostById(int id) {
            return restClient.get()
                    .uri("/posts/{id}", id)
                    .retrieve()
                    .body(Post.class);
        }
    }

    ☑️ This service makes HTTP requests using RestClient.

    🔹 Step 3: Mock API Responses in Unit Tests

    import static org.junit.jupiter.api.Assertions.*;
    import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
    import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
    
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.client.MockRestServiceServer;
    import org.springframework.web.client.RestClient;
    import org.springframework.http.client.ClientHttpRequestFactory;
    
    public class PostServiceTest {
        
        private MockRestServiceServer mockServer;
        private PostService postService;
        
        @BeforeEach
        void setUp() {
            RestClient restClient = RestClient.builder().baseUrl("https://jsonplaceholder.typicode.com").build();
            mockServer = MockRestServiceServer.createServer((ClientHttpRequestFactory) restClient);
            postService = new PostService(restClient);
        }
    
        @Test
        void testGetPostById() {
            String mockResponse = "{\"id\": 1, \"title\": \"Mock Post\", \"body\": \"This is a test\"}";
    
            mockServer.expect(requestTo("https://jsonplaceholder.typicode.com/posts/1"))
                      .andRespond(withSuccess(mockResponse, MediaType.APPLICATION_JSON));
    
            Post post = postService.getPostById(1);
    
            assertNotNull(post);
            assertEquals(1, post.getId());
            assertEquals("Mock Post", post.getTitle());
        }
    }

    ☑️ This test mocks an API response and verifies the returned Post object.

    2. Writing Integration Tests

    Integration tests validate the end-to-end behavior of your application by making real API calls.

    🔹 Step 1: Enable Spring Boot Testing

    Use @SpringBootTest to run tests in a real Spring Boot environment.

    import static org.junit.jupiter.api.Assertions.*;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.http.ResponseEntity;
    
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class PostControllerTest {
    
        @Autowired
        private TestRestTemplate restTemplate;
    
        @Test
        void testGetPostById() {
            ResponseEntity<Post> response = restTemplate.getForEntity("/api/posts/1", Post.class);
    
            assertNotNull(response.getBody());
            assertEquals(1, response.getBody().getId());
        }
    }

    ☑️ Runs the entire Spring Boot application and tests the real API.

    3. Using WireMock for Advanced Mocking

    WireMock is useful when you need more control over mock API responses, such as:

    • Simulating network delays
    • Returning different status codes
    • Mocking authentication flows

    🔹 Step 1: Add WireMock Dependency

    <dependency>
        <groupId>org.wiremock.integrations</groupId>
        <artifactId>wiremock-spring-boot</artifactId>
        <version>3.6.0</version>
        <scope>test</scope>
    </dependency>

    ☑️ This integrates WireMock into Spring Boot.

    🔹 Step 2: Configure WireMock for Testing

    import static com.github.tomakehurst.wiremock.client.WireMock.*;
    import static org.junit.jupiter.api.Assertions.*;
    
    import com.github.tomakehurst.wiremock.WireMockServer;
    import org.junit.jupiter.api.*;
    
    public class WireMockTest {
        
        private static WireMockServer wireMockServer;
    
        @BeforeAll
        static void startWireMock() {
            wireMockServer = new WireMockServer(8089);
            wireMockServer.start();
        }
    
        @Test
        void testWireMockAPI() {
            wireMockServer.stubFor(get(urlEqualTo("/posts/1"))
                    .willReturn(aResponse()
                            .withHeader("Content-Type", "application/json")
                            .withBody("{\"id\":1, \"title\":\"Mocked Post\"}")));
    
            String response = restClient.get()
                    .uri("http://localhost:8089/posts/1")
                    .retrieve()
                    .body(String.class);
    
            assertTrue(response.contains("Mocked Post"));
        }
    
        @AfterAll
        static void stopWireMock() {
            wireMockServer.stop();
        }
    }

    ☑️ WireMock allows powerful API simulations with request matching and response control.

    Leave a Reply

    Your email address will not be published. Required fields are marked *