Raccourcir une URL en Java consiste à effectuer un POST via java.net.http.HttpClient. Construisez la requête, définissez votre clé API dans un en-tête Bearer, envoyez-la, puis lisez short_url dans le corps de la réponse. Le client est inclus dans le JDK depuis Java 11, si bien que la première version fonctionnelle ne nécessite aucune dépendance.
La plupart des résultats Java sur ce sujet parlent de « construire un raccourcisseur d'URL avec Spring Boot et JPA », ce qui concerne le problème du stockage, pas celui-ci. Si c'est ce que vous cherchez, comment construire un raccourcisseur d'URL en présente la conception ; si vous voulez obtenir un lien court depuis un service existant, commencez par la présentation de l'API gratuite de raccourcissement d'URL pour connaître la forme du point de terminaison et l'authentification.
Le même guide existe pour les autres environnements d'exécution : Python, JavaScript, Go et Ruby.
La méthode la plus rapide : une requête via un client partagé
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public final class Shortener {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
public static void main(String[] args) throws Exception {
String body = """
{"destination_url":"https://example.com/spring-sale?utm_source=newsletter"}""";
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException("shorten failed: " + response.statusCode());
}
System.out.println(response.body()); // {"id":"...","short_url":"https://s.elido.me/ab12cd"}
}
}
Deux délais d'expiration, pour deux fonctions différentes. connectTimeout du builder limite la durée de la négociation TCP et TLS ; timeout de la requête limite l'échange complet. Si vous ne définissez que le premier, un serveur qui accepte votre connexion puis ne répond plus peut bloquer le thread indéfiniment.
La vérification statusCode() >= 400 n'est pas une précaution superflue : elle fait partie du contrat. send lève IOException lorsque la socket tombe et HttpTimeoutException lorsque l'échéance est dépassée, mais jamais pour une erreur HTTP. Une clé révoquée produit un objet de réponse parfaitement normal contenant une erreur JSON dans le corps.
Dans du vrai code, analysez le corps avec Jackson. Un text block convient honnêtement pour un champ codé en dur, mais cesse de convenir dès qu'une URL de destination contient un guillemet.
Réutiliser un seul client, pas un client par appel
HttpClient est thread-safe et immuable une fois construit, et chaque instance possède un pool de connexions ainsi qu'un exécuteur. En construire un dans une méthode utilitaire signifie que chaque appel recommence la négociation TLS et laisse un exécuteur que le ramasse-miettes devra remarquer plus tard. La règle tient en une phrase : une instance statique par application.
Réessayer sur 429 et 5xx sans créer de doublons
Il y a un échec qu'il vaut la peine de prévoir. Le POST arrive, le lien est créé, mais la réponse se perd sur le chemin du retour. Votre code constate un délai d'expiration, effectue une nouvelle tentative et, à présent, deux liens courts pointent vers une même destination.
Une Idempotency-Key règle le problème. En dériver la valeur de la destination plutôt que d'un UUID aléatoire signifie qu'une nouvelle exécution du même lot est gratuite au lieu de créer des doublons.
static String shorten(String destination, int attempts) throws Exception {
String key = HexFormat.of().formatHex(
MessageDigest.getInstance("SHA-256").digest(destination.getBytes(UTF_8)));
for (int attempt = 0; attempt < attempts; attempt++) {
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(
Map.of("destination_url", destination))))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 400) {
return MAPPER.readTree(response.body()).get("short_url").asText();
}
if (status == 429) {
Thread.sleep(Duration.ofSeconds(response.headers()
.firstValue("Retry-After").map(Long::parseLong).orElse(2L)));
} else if (status >= 500) {
Thread.sleep(Duration.ofSeconds(1L << attempt)); // 1s, 2s, 4s
} else {
throw new IllegalStateException("shorten failed: " + status + " " + response.body());
}
}
throw new IllegalStateException("shorten failed after " + attempts + " attempts");
}
Un 401 ou un 422 s'arrête au premier passage au lieu de consommer trois tentatives pour un problème qui ne changera pas. Seuls les 429 et les 5xx justifient d'attendre, et la branche 429 respecte la valeur fournie par le serveur au lieu de la deviner. Le raisonnement complet se trouve dans l'analyse approfondie des limites de débit et de l'idempotence.
Vous voulez l'exécuter sur un point de terminaison réel ? Créez une clé avec le forfait gratuit, exportez ELIDO_API_KEY, et le code ci-dessus se compile et s'exécute tel quel avec Java 21.
En masse : des threads virtuels avec un plafond
Les threads virtuels sont arrivés pour de bon dans Java 21 et changent la forme de ce problème. Les E/S bloquantes sur un thread virtuel ne coûtent presque rien : un thread par URL n'est donc plus une pratique imprudente. En revanche, la limite de débit n'a pas bougé, ce qui explique pourquoi le Semaphore compte davantage que l'exécuteur.
static Map<String, String> shortenAll(List<String> urls) throws Exception {
Map<String, String> out = new ConcurrentHashMap<>();
Semaphore gate = new Semaphore(8); // 8 requests in flight, whatever urls.size() is
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String url : urls) {
executor.submit(() -> {
gate.acquire();
try {
out.put(url, shorten(url, 3));
} catch (Exception e) {
out.put(url, "ERROR: " + e.getMessage()); // one bad URL, not a dead batch
} finally {
gate.release();
}
return null;
});
}
} // close() waits for every task to finish
return out;
}
Le bloc try-with-resources fait réellement le travail : fermer un exécuteur de threads virtuels attend la fin de toutes les tâches soumises, si bien qu'il n'y a pas de danse awaitTermination. La ConcurrentHashMap indexée par l'URL d'origine rend visible un échec partiel et permet de répéter l'exécution.
De Java 11 à 17, la même approche fonctionne avec sendAsync et un pool de threads fixe. En revanche, quelle que soit la version, CompletableFuture.allOf appliqué à une liste sans limite ne fonctionne pas : il lance tout en même temps et accumule une pile de 429.
Quand un client généré devient utile
Pour un seul point de terminaison, le code ci-dessus constitue toute l'intégration et une dépendance n'apporte rien. La situation change dès que vous listez des liens avec pagination, filtrez par tag et mappez une demi-douzaine de formes de réponse : des modèles générés et un client qui connaît déjà les règles de nouvelle tentative sont alors vite rentabilisés. La page API et SDK contient la liste actuelle, et le guide de démarrage rapide du SDK montre la version typée de ce même appel.
Dans tous les cas, les bonnes habitudes restent les mêmes : un client partagé, une échéance sur chaque requête, le code d'état lu avant le corps et une clé d'idempotence stable pour tout ce qui peut être exécuté deux fois. Une fois les liens créés depuis un service plutôt que depuis un ordinateur portable, les webhooks pour les événements de liens sont plus efficaces que l'interrogation périodique pour savoir ce qui s'est passé ensuite.
Lire la série pilier
Cet article appartient au cluster engineering. Commencez par le guide de l'API gratuite de raccourcissement d'URL, puis consultez les limites de débit et l'idempotence. La référence à jour se trouve dans la documentation de l'API, et les solutions pour développeurs couvrent le reste des fonctionnalités.
À lire aussi sur le blog
Questions fréquentes
Comment raccourcir une URL en Java ?
Construisez un POST avec HttpRequest, envoyez-le via un HttpClient partagé et lisez short_url dans le corps de la réponse. java.net.http est inclus dans le JDK depuis Java 11, donc une version fonctionnelle ne nécessite aucune dépendance Maven : définissez l'en-tête Authorization, envoyez un petit corps JSON et vérifiez statusCode() avant l'analyse.
Ai-je besoin d'OkHttp ou d'Apache HttpClient pour appeler une API REST en Java ?
Pas pour cela. java.net.http.HttpClient, intégré au JDK, gère les POST JSON, les délais d'expiration et les envois asynchrones. Un client tiers vaut la peine lorsque vous avez besoin d'intercepteurs, de métriques de connexion ou de la gestion du push HTTP/2 que le client du JDK n'expose pas, ce qui n'est pas nécessaire pour un simple appel de création.
Le HttpClient de Java lève-t-il une exception pour un 404 ou un 500 ?
Non. Il lève uniquement IOException en cas d'échec du transport et HttpTimeoutException lorsque le délai d'expiration de la requête est atteint. Un 4xx ou un 5xx revient sous la forme d'un HttpResponse normal : vous devez donc lire statusCode() vous-même. Oublier cette vérification est généralement la raison pour laquelle une clé révoquée semble correspondre à un appel réussi.
Dois-je créer un nouveau HttpClient pour chaque requête ?
Non. Chaque HttpClient possède son propre pool de connexions et son propre exécuteur : en construire un par requête empêche de réutiliser les connexions et peut accumuler les threads. Créez une instance statique pour l'application et partagez-la ; la classe est documentée comme thread-safe et conçue pour être réutilisée.
Comment raccourcir de nombreuses URL en une seule fois en Java ?
Exécutez les appels avec un exécuteur de threads virtuels et un Semaphore qui plafonne le nombre de requêtes simultanées, généralement autour de huit. Les threads virtuels rendent suffisamment peu coûteux le modèle un thread par URL pour qu'il reste raisonnable, mais la limite de débit de l'API n'a pas changé : c'est donc le sémaphore qui permet réellement au lot d'aller jusqu'au bout.
Comment construire le corps JSON sans bibliothèque ?
Pour un champ unique, un text block contenant la valeur échappée convient. Dès que le corps contient des chaînes fournies par l'utilisateur ou plus de deux champs, utilisez Jackson ou une implémentation JSON-B. Un JSON construit à la main casse dès que la première URL de destination contient un guillemet, et cette erreur ressemble alors à une erreur du serveur plutôt qu'à un problème de formatage.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Gratuit, sans inscription · 2 par jour