Lately, I’ve been quite busy, but I finally found some time to share my small issues and ways to solve them. In my project, I often communicate with external systems via REST using FeignClient. Many times, I encounter situations where the external API returns not only the expected data but also error messages in the form of objects. In this short article, I’ll show how I approached this and how you can handle different types of responses, mapping both data and errors to appropriate DTO objects.
A common problem occurs when an external API, instead of returning an empty response or array, returns an object with error information, causing issues with data mapping. This can result in exceptions or incorrect error handling. Sample error objects may contain fields like error code, message, validity, and additional information.
The solution to this problem is to implement a custom error-handling mechanism in FeignClient that intercepts responses other than 200 OK and maps them to the appropriate DTO objects.
1: Creating DTO for Errors
First, we need to define a DTO class that will reflect the error response structure returned by the external API. For example:
public class ExternalError {
private String code;
private String message;
private String validity;
private String info;
// Getters and setters
}
2: Implementing Custom ErrorDecoder
Next, we need to create a custom ErrorDecoder that will intercept error responses and map them to the DTO object. I use ObjectMapper to map the response to an ExternalError object. I also throw an ExternalApiException that contains error details.
import feign.Response;
import feign.codec.ErrorDecoder;
import com.fasterxml.jackson.databind.ObjectMapper;
public class CustomErrorDecoder implements ErrorDecoder {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Exception decode(String methodKey, Response response) {
try {
// Mapping the error response to ExternalError
ExternalError errorResponse = objectMapper.readValue(response.body().asInputStream(), ExternalError.class);
// Throwing an exception with full error information
return new ExternalApiException("Error: " + errorResponse.getMessage(), errorResponse);
} catch (Exception e) {
return new ExternalApiException("Error decoding error response: " + e.getMessage());
}
}
}
3: Creating ExternalApiException
To handle the thrown exceptions, we define the ExternalApiException class, which will contain both the error message and the full error DTO object.
public class ExternalApiException extends RuntimeException {
private ExternalError error;
public ExternalApiException(String message, ExternalError error) {
super(message);
this.error = error;
}
public ExternalError getError() {
return error;
}
}
4: Configuring FeignClient to use custom ErrorDecoder:
@Configuration
public class FeignClientConfig {
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
@FeignClient(name = "externalApiClient", url = "https://external-api.com", configuration = FeignClientConfig.class)
public interface ExternalApiClient {
@GetMapping("/resource/{id}")
MyObject getResourceById(@PathVariable("id") Long id);
}
5: Handling Exceptions in the Controller
Finally, in our controller, we can handle the exceptions thrown by FeignClient and return an appropriate HTTP response:
@RestController
public class MyController {
@Autowired
private ExternalApiClient externalApiClient;
@GetMapping("/myresource/{id}")
public ResponseEntity<?> getMyResource(@PathVariable Long id) {
try {
MyObject myObject = externalApiClient.getResourceById(id);
return ResponseEntity.ok(myObject);
} catch (ExternalApiException e) {
ExternalError error = e.getError();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Error: " + error.getMessage() + ", Code: " + error.getCode());
}
}
}
In this case, if FeignClient returns an error response, it will be intercepted by ExternalApiException, and the appropriate message will be returned to the client.
Handling errors in FeignClient using a custom ErrorDecoder and DTO objects allows for greater control over responses from external systems. This way, we can safely and effectively manage both correct data and errors returned by the API.