Intégrer une API IA (OpenAI ou Claude) dans une application Java
Guide pratique pour appeler les APIs OpenAI et Anthropic Claude depuis Java : HTTP client, streaming, gestion des erreurs, coûts et bonnes pratiques de production.
Intégrer une API IA dans une application Java n’est pas plus compliqué qu’appeler n’importe quelle API REST. La vraie complexité est ailleurs : gérer les timeouts longs, le streaming, les coûts, les erreurs de rate limit et la fiabilité en production. Ce guide couvre les points concrets.
Dépendances et configuration
Pour OpenAI, le SDK officiel Java est disponible depuis 2024 :
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>0.8.0</version>
</dependency>
Pour Claude (Anthropic), utilisez le client HTTP standard (OkHttp ou Spring’s RestClient) — le SDK Java officiel Anthropic est en bêta :
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
Stockez la clé API dans une variable d’environnement, jamais dans le code :
# application.yml
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-4o
max-tokens: 2000
timeout-seconds: 60
Appel simple avec OpenAI SDK Java
@Service
public class AiTextService {
private final OpenAIClient client;
private final String model;
public AiTextService(
@Value("${openai.api-key}") String apiKey,
@Value("${openai.model}") String model) {
this.client = OpenAIOkHttpClient.builder()
.apiKey(apiKey)
.build();
this.model = model;
}
public String generateText(String systemPrompt, String userMessage) {
ChatCompletion completion = client.chat().completions().create(
ChatCompletionCreateParams.builder()
.model(model)
.maxTokens(2000)
.messages(List.of(
ChatCompletionMessageParam.ofSystem(systemPrompt),
ChatCompletionMessageParam.ofUser(userMessage)
))
.build()
);
return completion.choices().get(0)
.message().content().orElse("");
}
}
Appel Claude via RestClient (Spring Boot 3.2+)
@Service
public class ClaudeService {
private final RestClient restClient;
private final String apiKey;
public ClaudeService(
RestClient.Builder builder,
@Value("${anthropic.api-key}") String apiKey) {
this.apiKey = apiKey;
this.restClient = builder
.baseUrl("https://api.anthropic.com/v1")
.defaultHeader("x-api-key", apiKey)
.defaultHeader("anthropic-version", "2023-06-01")
.defaultHeader("Content-Type", "application/json")
.build();
}
public String generate(String systemPrompt, String userMessage) {
ClaudeRequest request = new ClaudeRequest(
"claude-opus-4-8",
List.of(new Message("user", userMessage)),
systemPrompt,
4096
);
ClaudeResponse response = restClient.post()
.uri("/messages")
.body(request)
.retrieve()
.body(ClaudeResponse.class);
return response.content().get(0).text();
}
}
Gestion des erreurs et retry
Les APIs IA retournent des erreurs 429 (rate limit) et 503 (service temporairement indisponible) qu’il faut gérer avec un retry exponentiel.
@Service
public class ResilientAiService {
private final AiTextService aiService;
public String generateWithRetry(String system, String user) {
int maxAttempts = 3;
long delayMs = 1000;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return aiService.generateText(system, user);
} catch (RateLimitException e) {
if (attempt == maxAttempts) throw e;
log.warn("Rate limit atteint, retry {} dans {}ms", attempt, delayMs);
Thread.sleep(delayMs * attempt); // backoff exponentiel
} catch (ServiceUnavailableException e) {
if (attempt == maxAttempts) throw e;
Thread.sleep(delayMs * attempt);
}
}
throw new AiServiceException("Échec après " + maxAttempts + " tentatives");
}
}
Avec Spring Retry, c’est plus concis :
@Retryable(
retryFor = { RateLimitException.class, ServiceUnavailableException.class },
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String generateText(String system, String user) { ... }
Timeouts : un point critique
Les appels IA peuvent durer 30 à 60 secondes pour de longs textes. Configurez des timeouts adaptés :
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS) // long pour le streaming
.writeTimeout(30, TimeUnit.SECONDS)
.build();
Ne bloquez pas un thread HTTP pendant 60 secondes en production. Privilégiez le streaming ou les appels asynchrones :
@Async
public CompletableFuture<String> generateAsync(String system, String user) {
return CompletableFuture.supplyAsync(() -> generateText(system, user));
}
Contrôler les coûts
Les coûts sont proportionnels aux tokens consommés. Stratégies pour les maîtriser :
Cache des réponses : si le même prompt revient fréquemment, mettez la réponse en cache (Redis, Caffeine) avec un TTL adapté.
@Cacheable(value = "ai-responses", key = "#systemPrompt + ':' + #userMessage")
public String generateText(String systemPrompt, String userMessage) { ... }
Limiter le contexte : n’envoyez que l’information nécessaire dans le prompt. Chaque token coûte. Un prompt de 5000 tokens coûte 5x plus qu’un prompt de 1000 tokens.
Choisir le bon modèle : GPT-4o mini ou Claude Haiku pour les tâches simples (classification, résumé court), les modèles puissants pour les analyses complexes.
Logger les consommations : loggez le nombre de tokens entrants/sortants pour chaque appel en production. C’est la seule façon de détecter une dérive de coût.
Conclusion
Intégrer une API IA en Java est simple techniquement. La vraie ingénierie est dans la robustesse : timeouts adaptés, retry sur les erreurs transitoires, cache pour les coûts et monitoring des consommations. Un appel IA non surveillé en production, c’est une facture surprise en fin de mois.
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013