Eine URL in Java zu kürzen bedeutet einen POST über java.net.http.HttpClient. Erstellen Sie die Anfrage, setzen Sie Ihren API-Schlüssel als Bearer-Header, senden Sie sie und lesen Sie short_url aus dem Antworttext. Der Client wird seit Java 11 mit dem JDK ausgeliefert, daher hat die erste funktionierende Version überhaupt keine Abhängigkeiten.
Die meisten Java-Ergebnisse dazu lauten "build a URL shortener with Spring Boot and JPA", behandeln also das Speicherproblem und nicht dieses. Wenn Sie das möchten, behandelt eine URL-Kürzer-App bauen das Design. Wenn Sie einen Kurzlink aus einem bestehenden Dienst möchten, beginnen Sie mit der Übersicht zur kostenlosen URL-Kürzer-API, um Aufbau und Authentifizierung des Endpunkts kennenzulernen.
Denselben Ablauf in den anderen Laufzeitumgebungen finden Sie für Python, JavaScript, Go und Ruby.
Der schnellste Weg: Eine Anfrage über einen gemeinsam verwendeten Client
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"}
}
}
Zwei Timeouts mit unterschiedlichen Aufgaben. connectTimeout am Builder begrenzt, wie lange der TCP- und TLS-Handshake dauern darf. timeout an der Anfrage begrenzt den gesamten Austausch. Wenn Sie nur das erste setzen, hält ein Server, der Ihre Verbindung akzeptiert und danach nichts mehr sagt, den Thread unbegrenzt fest.
Die Prüfung statusCode() >= 400 ist keine defensive Programmierung, sondern der Vertrag. send löst IOException aus, wenn der Socket ausfällt, und HttpTimeoutException, wenn die Frist abläuft, aber nie bei einem HTTP-Fehler. Ein widerrufener Schlüssel erzeugt ein völlig gewöhnliches Antwortobjekt mit einem JSON-Fehler im Body.
Parsen Sie den Body in echtem Code mit Jackson. Ein Textblock ist für ein einzelnes fest codiertes Feld sinnvoll und verliert seine Berechtigung, sobald eine Ziel-URL ein Anführungszeichen enthält.
Einen Client wiederverwenden, nicht für jeden Aufruf neu erstellen
HttpClient ist thread-sicher und nach der Erstellung unveränderlich. Jede Instanz besitzt außerdem einen Verbindungspool und einen Executor. Wenn Sie einen Client in einer Hilfsmethode erstellen, wird bei jedem Aufruf der TLS-Handshake wiederholt und ein Executor zurückgelassen, um den sich später der Garbage Collector kümmern muss. Eine statische Instanz pro Anwendung ist die gesamte Regel.
Bei 429 und 5xx wiederholen, ohne Duplikate zu erstellen
Es gibt einen Fehler, auf den Sie sich vorbereiten sollten. Der POST kommt an, der Link wird erstellt, aber die Antwort geht auf dem Rückweg verloren. Ihr Code sieht ein Timeout, versucht es erneut, und nun verweisen zwei Kurzlinks auf dasselbe Ziel.
Ein Idempotency-Key behebt das. Wenn Sie diesen Schlüssel aus dem Ziel statt aus einer zufälligen UUID ableiten, ist eine erneute Ausführung desselben Batches kostenlos statt doppelt.
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");
}
Ein 401 oder 422 beendet den ersten Durchlauf, statt drei Versuche mit etwas zu verbrauchen, das sich nie ändern wird. Nur 429 und 5xx rechtfertigen eine Wartezeit, und der 429-Zweig beachtet die Zahl des Servers, statt zu raten. Die gesamte Begründung finden Sie im Deep-Dive zu Rate-Limits und Idempotenz.
Möchten Sie den Code gegen einen aktiven Endpunkt ausführen? Erstellen Sie einen Schlüssel im kostenlosen Tarif, exportieren Sie ELIDO_API_KEY, und der obige Code lässt sich unter Java 21 unverändert kompilieren und ausführen.
Massenkürzung: Virtuelle Threads mit Obergrenze
Virtuelle Threads sind in Java 21 endgültig eingeführt worden und verändern die Form dieses Problems. Blockierendes IO auf einem virtuellen Thread kostet fast nichts, daher ist ein Thread pro URL nicht mehr leichtsinnig. Das Rate-Limit hat sich allerdings nicht bewegt, weshalb das Semaphore wichtiger ist als der 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;
}
Der Try-with-resources-Block leistet echte Arbeit: Das Schließen eines Executors für virtuelle Threads wartet auf alle übermittelten Tasks, daher gibt es keinen Umweg über awaitTermination. Eine ConcurrentHashMap, deren Schlüssel die ursprüngliche URL ist, macht Teilausfälle sichtbar und hält die Ausführung wiederholbar.
Unter Java 11 bis 17 funktioniert dieselbe Form mit sendAsync und einem festen Thread-Pool. Was in keiner Version funktioniert, ist CompletableFuture.allOf über einer unbegrenzten Liste: Es startet alles gleichzeitig und sammelt eine Menge von 429-Fehlern.
Wann ein generierter Client seinen Platz verdient
Für einen Endpunkt ist der obige Code die gesamte Integration, und eine Abhängigkeit bringt keinen Vorteil. Das ändert sich, sobald Sie Links mit Pagination auflisten, nach Tags filtern und ein halbes Dutzend Antwortformen abbilden: Generierte Modelle und ein Client, der die Retry-Regeln bereits kennt, machen sich bezahlt. Die Seite API und SDKs enthält die aktuelle Liste, und der SDK-Schnellstart zeigt die typisierte Version desselben Aufrufs.
So oder so bleiben die Gewohnheiten gleich: ein gemeinsam verwendeter Client, eine Frist für jede Anfrage, der gelesene Statuscode vor dem Body und ein stabiler Idempotency-Key für alles, was zweimal ausgeführt werden könnte. Sobald Links von einem Dienst statt von einem Laptop erstellt werden, sind Webhooks für Link-Ereignisse besser geeignet als Polling, um herauszufinden, was als Nächstes passiert ist.
Die Cornerstone-Serie lesen
Dieser Beitrag gehört zum Engineering-Cluster. Beginnen Sie mit dem Leitfaden zur kostenlosen URL-Kürzer-API, danach mit Rate-Limits und Idempotenz. Die aktuelle Referenz sind die API-Dokumente, und Lösungen für Entwickler deckt den übrigen Funktionsumfang ab.
Weitere Beiträge im Blog
Häufig gestellte Fragen
Wie kürze ich eine URL in Java?
Erstellen Sie mit HttpRequest einen POST, senden Sie ihn über einen gemeinsam verwendeten HttpClient und lesen Sie short_url aus dem Antworttext. java.net.http ist seit Java 11 im JDK enthalten, daher benötigt eine funktionierende Version überhaupt keine Maven-Abhängigkeit: Setzen Sie den Authorization-Header, senden Sie einen kleinen JSON-Body und prüfen Sie statusCode(), bevor Sie ihn parsen.
Brauche ich OkHttp oder Apache HttpClient, um eine REST-API in Java aufzurufen?
Nicht dafür. Der integrierte java.net.http.HttpClient deckt einen JSON-POST, Timeouts und asynchrone Sendungen ab. Ein Drittanbieter-Client lohnt sich, wenn Sie Interceptors, Verbindungsmetriken oder HTTP/2-Push-Verarbeitung benötigen, die der JDK-Client nicht bereitstellt, was ein einzelner Erstellungsaufruf nicht tut.
Löst der HttpClient von Java bei einem 404 oder 500 eine Exception aus?
Nein. Er löst nur bei einem Transportfehler eine IOException und bei Ablauf des Request-Timeouts eine HttpTimeoutException aus. Ein 4xx- oder 5xx-Status kommt als normales HttpResponse zurück, daher müssen Sie statusCode() selbst lesen. Diese Prüfung zu überspringen ist der häufigste Grund, warum ein widerrufener Schlüssel wie ein erfolgreicher Aufruf aussieht.
Sollte ich für jede Anfrage einen neuen HttpClient erstellen?
Nein. Jeder HttpClient verfügt über seinen eigenen Verbindungspool und Executor. Wenn Sie pro Anfrage einen erstellen, verzichten Sie auf die Wiederverwendung von Verbindungen und können Threads anhäufen. Erstellen Sie eine statische Instanz für die Anwendung und verwenden Sie sie gemeinsam. Die Klasse ist als thread-sicher dokumentiert und für die Wiederverwendung ausgelegt.
Wie kürze ich viele URLs gleichzeitig in Java?
Führen Sie die Aufrufe auf einem Executor für virtuelle Threads aus, und begrenzen Sie mit einem Semaphore, wie viele gleichzeitig laufen, normalerweise etwa acht. Virtuelle Threads machen einen Thread pro URL günstig genug, um sinnvoll zu sein, aber das Rate-Limit der API hat sich nicht geändert. Daher hält das Semaphore den Batch tatsächlich am Laufen.
Wie erstelle ich den JSON-Body ohne Bibliothek?
Für ein einzelnes Feld genügt ein Textblock mit maskiertem Wert. Sobald der Body vom Benutzer gelieferte Zeichenketten oder mehr als zwei Felder enthält, sollten Sie Jackson oder eine JSON-B-Implementierung verwenden. Von Hand erstelltes JSON geht bei der ersten Ziel-URL kaputt, die ein Anführungszeichen enthält, und dieser Fehler sieht wie ein Serverfehler statt wie ein Formatierungsfehler aus.
Elido testen
URL einfügen, kurzer Link in Sekunden
Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.
Kostenlos, keine Anmeldung erforderlich · 2 pro Tag