Designing Third-Party API Integration in Java Applications

Designing Third-Party API Integration in Java Applications

When building enterprise aplications, integrating with external services is a common requirement. This document outlines practical approaches for implementing third-party API calls in Java, covering both synchronous and asynchronous patterns.

Core Architecture

A robust API integration layer typically consists of three primary components:

  1. Client Layer: Encapsulates HTTP communication details
  2. Service Layer: Handles business logic and data transformation
  3. Exception Handling: Manages errors and provides fallback mechanisms

HTTP Client Implementation

For RESTful API communication, using RestTemplate with proper configuration ensures reliable connections:

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

public class ApiClient {
    private final RestTemplate restTemplate;
    private final String baseUrl;
    
    public ApiClient(RestTemplate restTemplate, String baseUrl) {
        this.restTemplate = restTemplate;
        this.baseUrl = baseUrl;
    }
    
    public String fetchResource(String endpoint) {
        try {
            String url = baseUrl + endpoint;
            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
            return response.getBody();
        } catch (RestClientException e) {
            throw new ApiException("Failed to fetch resource from " + endpoint, e);
        }
    }
    
    public <T> T postData(String endpoint, Object payload, Class<T> responseType) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
        HttpEntity<Object> request = new HttpEntity<>(payload, headers);
        
        ResponseEntity<T> response = restTemplate.exchange(
            baseUrl + endpoint,
            HttpMethod.POST,
            request,
            responseType
        );
        return response.getBody();
    }
}

Data Transfer Objects

Create dedicated DTOs for API communication to decouple your domain model from external contracts:

public class ApiResponse<T> {
    private boolean success;
    private T data;
    private String errorMessage;
    private int statusCode;
    
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setSuccess(true);
        response.setData(data);
        return response;
    }
    
    public static <T> ApiResponse<T> failure(String message, int code) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setSuccess(false);
        response.setErrorMessage(message);
        response.setStatusCode(code);
        return response;
    }
    
    // getters and setters
}

Exception Handling Strategy

Implement a comprehensive exception hierarchy for different failure scenarios:

public class ApiException extends RuntimeException {
    private final int httpStatus;
    private final String errorCode;
    
    public ApiException(String message, int httpStatus, String errorCode) {
        super(message);
        this.httpStatus = httpStatus;
        this.errorCode = errorCode;
    }
    
    public ApiException(String message, Throwable cause) {
        super(message, cause);
        this.httpStatus = 500;
        this.errorCode = "UNKNOWN";
    }
    
    public int getHttpStatus() {
        return httpStatus;
    }
    
    public String getErrorCode() {
        return errorCode;
    }
}

public class ApiExceptionHandler {
    public String handleException(ApiException e) {
        return switch (e.getHttpStatus()) {
            case 400 -> "Invalid request parameters";
            case 401 -> "Authentication failed";
            case 403 -> "Access denied";
            case 404 -> "Resource not found";
            case 500 -> "External service unavailable";
            default -> "Unexpected error occurred";
        };
    }
}

Resilient Communication Patterns

For production environments, implement retry logic and circuit breakers:

public class ResilientApiClient {
    private final RestTemplate restTemplate;
    private final int maxRetries;
    private final long retryDelay;
    
    public ResilientApiClient(RestTemplate restTemplate, int maxRetries, long retryDelay) {
        this.restTemplate = restTemplate;
        this.maxRetries = maxRetries;
        this.retryDelay = retryDelay;
    }
    
    public String callWithRetry(String url) {
        int attempts = 0;
        while (attempts < maxRetries) {
            try {
                return restTemplate.getForObject(url, String.class);
            } catch (RestClientException e) {
                attempts++;
                if (attempts >= maxRetries) {
                    throw new ApiException("Max retry attempts reached", e);
                }
                try {
                    Thread.sleep(retryDelay * attempts);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new ApiException("Retry interrupted", ie);
                }
            }
        }
        throw new ApiException("Request failed after " + maxRetries + " attempts");
    }
}

SOAP Service Integration

For legacy systems requiring SOAP endpoints, use the generated client stubs:

import javax.xml.ws.soap.SOAPFaultException;

public class SoapServiceClient {
    private final YourServicePortType port;
    
    public SoapServiceClient(YourServicePortType port) {
        this.port = port;
    }
    
    public String invokeOperation(RequestType request) {
        try {
            ResponseType response = port.operation(request);
            return response.getResult();
        } catch (SOAPFaultException e) {
            throw new ApiException("SOAP fault: " + e.getMessage(), 500, "SOAP_ERROR");
        }
    }
}

Component Interaction Flow

sequenceDiagram
    participant Consumer
    participant ApiClient as API Client
    participant ResilientClient as Resilient Layer
    participant ExternalService as Third-Party API
    
    Consumer->>ApiClient: sendRequest(endpoint, payload)
    ApiClient->>ResilientClient: executeWithRetry()
    ResilientClient->>ExternalService: HTTP Request
    
    alt Success
        ExternalService-->>ResilientClient: 200 OK + Data
        ResilientClient-->>ApiClient: Parsed Response
        ApiClient-->>Consumer: Business Result
    else Transient Failure
        ExternalService-->>ResilientClient: Timeout/5xx
        ResilientClient->>ExternalService: Retry Attempt
    else Permanent Failure
        ExternalService-->>ResilientClient: 4xx Error
        ResilientClient--xApiClient: ApiException
        ApiClient--xConsumer: Transformed Error
    end

Service Component Design

classDiagram
    class ApiClient {
        -RestTemplate template
        -String baseUrl
        +fetchResource(endpoint) String
        +postData(endpoint, payload, type) T
    }
    
    class ResilientApiClient {
        -int maxRetries
        -long retryDelay
        +callWithRetry(url) String
        -executeWithBackoff() String
    }
    
    class ApiException {
        -int httpStatus
        -String errorCode
        +getHttpStatus() int
        +getErrorCode() String
    }
    
    class ApiResponse {
        -boolean success
        -T data
        -String errorMessage
        +success(data) ApiResponse
        +failure(message, code) ApiResponse
    }
    
    ApiClient --> ResilientApiClient : uses
    ResilientApiClient --> ApiException : throws
    ApiClient --> ApiResponse : returns

Tags: java api-integration rest-client error-handling Spring

Posted on Fri, 14 Aug 2026 16:35:43 +0000 by colake