The Feign client is a declarativeREST client that makes writing web clients easier. When using Feign, the developer has only to define the interfaces and annotate them accordingly. The actual web client implementation is then provided by Spring at runtime.
For each request, the assigned thread blocks until it receives the response. The disadvantage of keeping multiple threads alive is that each open thread occupies memory and CPU cycles.
Setting up Http header configuration and error handling
Define the class of output and error class of http call response
Define the client(interface) of HTTP call
Web Client
Introduction
The WebClient is part of the Spring WebFlux library. It’s a non-blocking solution provided by the Spring Reactive Framework
WebClient executes the HTTP request and adds a “waiting for response” task into a queue. Later, the “waiting for response” task is executed from the queue after the response is received, finally delivering the response to the subscriber function.
Implementation
Add the dependencies
Setting up web client with http call detail
Define the class of streaming response and controller level for response type
Comparison
Open Feign will be more developer-friendly, easy to implement through interface layer. Most are likely suitable for CRUD operation
For handling streaming, Feign’s design is request → response → close connection, streaming is not supported out of the box. In that case, Web Client will be better.
Although the Feign client is a great choice in many cases and the resulting code has lower cognitive complexity, the non-blocking style of WebClient uses much fewer system resources during high-traffic spikes
package com.example.maven_playground.config;
import feign.RequestInterceptor;
import feign.codec.ErrorDecoder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatFeignConfig {
@Value("${chat.api.key}")
private String apiKey;
@Bean
public RequestInterceptor chatApiRequestInterceptor() {
return requestTemplate -> {
requestTemplate.header("Content-Type", "application/json");
requestTemplate.header("X-API-Key", apiKey);
};
}
@Bean
public ErrorDecoder chatErrorDecoder() {
return new ChatFeignErrorDecoder();
}
}
ChatFeignErrorDecoder.java
package com.example.maven_playground.config;
import com.example.maven_playground.dto.ChatErrorResponse;
import com.example.maven_playground.exception.ChatApiException;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.io.InputStream;
// Decode the response from another service
@Slf4j
public class ChatFeignErrorDecoder implements ErrorDecoder {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Exception decode(String methodKey, Response response) {
HttpStatus status = HttpStatus.valueOf(response.status());
String detail = "Unknown error from Chat API";
try (InputStream bodyIs = response.body().asInputStream()) {
ChatErrorResponse errorResponse = objectMapper.readValue(
bodyIs,
ChatErrorResponse.class);
if (errorResponse.getDetail() != null) {
detail = errorResponse.getDetail();
}
} catch (IOException e) {
log.error("Error reading error response body", e);
}
log.error("Chat API error - Status: {}, Detail: {}", status, detail);
return new ChatApiException(status, detail);
}
}
package com.example.maven_playground.exception;
import com.example.maven_playground.dto.ChatErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// Handle the output response of http call
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ChatApiException.class)
public ResponseEntity<ChatErrorResponse> handleChatApiException(ChatApiException ex) {
log.error("Chat API error: {}", ex.getDetail());
ChatErrorResponse errorResponse = ChatErrorResponse.builder()
.detail(ex.getDetail())
.build();
return ResponseEntity.status(ex.getStatus()).body(errorResponse);
}
}
ChatResponse.java
package com.example.maven_playground.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatResponse {
// Add fields based on the actual API response
// For now, assuming a simple response structure
private String answer;
private String status;
// Add more fields as needed based on actual API response
}