Accorciare un URL in Java richiede un solo POST tramite java.net.http.HttpClient. Costruisci la richiesta, imposta la tua chiave API come header Bearer, inviala e leggi short_url dal corpo della risposta. Il client è incluso nel JDK da Java 11, quindi la prima versione funzionante non ha alcuna dipendenza.
La maggior parte dei risultati Java per questo caso riguarda il "build di un URL shortener con Spring Boot e JPA", che è il problema dell'archiviazione, non questo. Se è ciò che vuoi, come costruire un URL shortener illustra il design; se vuoi ottenere uno short link da un servizio esistente, inizia dalla panoramica dell'API gratuita per URL shortener per la struttura dell'endpoint e l'autenticazione.
Lo stesso walkthrough negli altri runtime: Python, JavaScript, Go e Ruby.
Il modo più veloce: una richiesta tramite un client condiviso
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"}
}
}
Due timeout, con funzioni diverse. connectTimeout sul builder limita la durata dell'handshake TCP e TLS; timeout sulla richiesta limita l'intero scambio. Imposta solo il primo e un server che accetta la connessione ma poi non risponde terrà il thread bloccato indefinitamente.
Il controllo statusCode() >= 400 non è programmazione difensiva, è il contratto. send genera IOException quando il socket si interrompe e HttpTimeoutException quando scade la deadline, ma mai per un errore HTTP. Una chiave revocata produce un oggetto risposta perfettamente normale con un errore JSON nel corpo.
Nel codice reale, analizza il corpo con Jackson. Un text block è una soluzione onesta per un campo codificato direttamente nel codice e smette di esserlo non appena un URL di destinazione contiene una virgoletta.
Riutilizza un client, non crearne uno per ogni chiamata
HttpClient è thread-safe e immutabile una volta costruito, e ogni istanza possiede un pool di connessioni e un executor. Costruirne uno dentro un metodo di supporto significa ripetere l'handshake TLS a ogni chiamata e lasciare poi un executor al garbage collector. Una singola istanza statica per applicazione: questa è l'intera regola.
Nuovi tentativi su 429 e 5xx senza creare duplicati
C'è un errore per cui vale la pena progettare una gestione. Il POST arriva, il link viene creato, ma la risposta si perde durante il ritorno. Il tuo codice vede un timeout, riprova e ora due short link puntano alla stessa destinazione.
Un Idempotency-Key risolve il problema, e derivare quella chiave dalla destinazione invece che da un UUID casuale fa sì che rieseguire lo stesso batch non costi nulla e non crei duplicati.
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 o un 422 termina al primo passaggio invece di consumare tre tentativi per qualcosa che non cambierà. Vale la pena attendere solo per 429 e 5xx, e il ramo 429 rispetta il valore indicato dal server invece di indovinarlo. Il ragionamento completo è nella guida approfondita su rate limit e idempotenza.
Vuoi eseguirlo su un endpoint attivo? Crea una chiave con il piano gratuito, esporta ELIDO_API_KEY e il codice qui sopra compilerà e funzionerà così com'è su Java 21.
In blocco: thread virtuali con un limite
I thread virtuali sono diventati una funzionalità stabile in Java 21 e cambiano la forma di questo problema. L'IO bloccante su un thread virtuale costa quasi nulla, quindi usare un thread per URL non è più sconsiderato. Il rate limit, però, non è cambiato, ed è per questo che Semaphore conta più dell'executor.
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;
}
Il blocco try-with-resources svolge un lavoro reale: chiudere un executor di thread virtuali attende il completamento di tutti i task inviati, quindi non serve fare acrobazie con awaitTermination. ConcurrentHashMap, indicizzata dall'URL originale, mantiene visibile un errore parziale e rende l'esecuzione ripetibile.
Da Java 11 a 17 la stessa struttura funziona con sendAsync e un pool di thread fisso. Quello che non funziona in nessuna versione è usare CompletableFuture.allOf su un elenco senza limite: avvia tutto contemporaneamente e raccoglie una valanga di 429.
Quando un client generato si dimostra utile
Per un singolo endpoint, il codice sopra è l'intera integrazione e una dipendenza non offre nulla in più. La situazione cambia quando elenchi link con paginazione, filtri per tag e trasformi una mezza dozzina di strutture di risposta: i modelli generati e un client che conosce già le regole per i nuovi tentativi si ripagano da soli. La pagina API e SDK contiene l'elenco aggiornato, mentre la guida rapida agli SDK mostra la versione tipizzata della stessa chiamata.
In ogni caso le abitudini sono le stesse: un client condiviso, una deadline per ogni richiesta, il codice di stato letto prima del corpo e una chiave di idempotenza stabile per tutto ciò che potrebbe essere eseguito due volte. Quando i link vengono creati da un servizio invece che da un laptop, i webhook per gli eventi dei link sono migliori del polling per scoprire cosa è successo dopo.
Leggi la serie cornerstone
Questo articolo fa parte del cluster engineering. Inizia dalla guida all'API gratuita per URL shortener, poi passa a rate limit e idempotenza. Il riferimento aggiornato è la documentazione dell'API, mentre le soluzioni per sviluppatori coprono il resto della piattaforma.
Articoli correlati sul blog
Domande frequenti
Come posso accorciare un URL in Java?
Costruisci un POST con HttpRequest, invialo tramite un HttpClient condiviso e leggi short_url dal corpo della risposta. java.net.http è nel JDK da Java 11, quindi per una versione funzionante non serve alcuna dipendenza Maven: imposta l'header Authorization, invia un piccolo corpo JSON e controlla statusCode() prima del parsing.
Mi servono OkHttp o Apache HttpClient per chiamare un'API REST in Java?
Non per questo. java.net.http.HttpClient, già integrato, gestisce un POST JSON, i timeout e gli invii asincroni. Un client di terze parti vale la pena quando ti servono interceptor, metriche delle connessioni o la gestione dell'HTTP/2 push che il client del JDK non espone, esigenze che una singola chiamata di creazione non ha.
HttpClient di Java genera un'eccezione per un 404 o un 500?
No. Genera IOException solo in caso di errore di trasporto e HttpTimeoutException quando scade il timeout della richiesta. Un 4xx o un 5xx arriva come un normale HttpResponse, quindi devi leggere statusCode() personalmente. Saltare questo controllo è il motivo più comune per cui una chiave revocata sembra produrre una chiamata riuscita.
Devo creare un nuovo HttpClient per ogni richiesta?
No. Ogni HttpClient ha il proprio pool di connessioni e il proprio executor, quindi costruirne uno per richiesta fa perdere il riutilizzo delle connessioni e può accumulare thread. Crea una singola istanza statica per l'applicazione e condividila: la classe è documentata come thread-safe e progettata per essere riutilizzata.
Come posso accorciare molti URL contemporaneamente in Java?
Esegui le chiamate su un executor di thread virtuali con un Semaphore che limiti quante sono in corso, di solito circa otto. I thread virtuali rendono abbastanza economico usare un thread per URL, ma il rate limit dell'API non è cambiato, quindi è il semaforo a mantenere effettivamente in vita il batch.
Come posso costruire il corpo JSON senza una libreria?
Per un singolo campo, va bene un text block con il valore sottoposto a escape. Quando il corpo contiene stringhe fornite dall'utente o più di due campi, usa Jackson o un'implementazione JSON-B. Il JSON costruito a mano si rompe al primo URL di destinazione che contiene una virgoletta, e quell'errore sembra un errore del server invece di un problema di formattazione.
Prova Elido
Incolla un URL, ottieni un link breve
Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.
Gratis, nessuna registrazione richiesta · 2 al giorno