Java HttpClient (Java 11+)
Modern uygulamalar nadiren tek başına çalışır. Bir hava durumu servisi çağırırsın, ödeme API'sine istek atarsın, microservice'ler birbirleriyle konuşur. HTTP, bu iletişimin evrensel dilidir. Ve Java 11'den itibaren, bu dili konuşmak için artık üçüncü parti kütüphanelere mecbur değilsin.
Java 11 ile gelen HttpClient API, modern HTTP iletişimi için sıfırdan tasarlandı. HTTP/2 desteği, asenkron istekler, builder pattern — hepsi kutudan çıkıyor. Eskiden HttpURLConnection ile yazdığın 30 satırlık kodu artık 5 satırda yazabilirsin.
HttpClient'ı bir posta servisi gibi düşün. Mektubu (request) hazırlarsın, zarfa koyarsın, adresini yazarsın. Posta servisine (HttpClient) verirsin. Servis gönderir, cevabı (response) alır ve sana teslim eder. Senkron modda postanede beklersin, asenkron modda "gelince haber ver" dersin.
URLConnection'ın Sınırları
Java'nın orijinal HTTP API'si HttpURLConnection, Java 1.1'den beri var. 1997 yılında tasarlandı — internette jQuery bile yokken. Kullanımı zahmetli ve modern HTTP ihtiyaçlarını karşılamıyor.
// Eski yol — HttpURLConnection ile basit bir GET bile bu kadar uzun
URL url = new URL("https://api.example.com/users/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int status = conn.getResponseCode();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
conn.disconnect();
System.out.println(response);Sorunlar:
Çok fazla boilerplate kod
Stream'leri elle yönetmek ve kapatmak gerekiyor
HTTP/2 desteği yok
Asenkron istek desteği yok
İmmutable değil — thread-safe kullanımı zor
POST body göndermek ayrı bir macera
Aynı işi yeni API ile yapalım:
// Yeni yol — HttpClient ile (Java 11+)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200
System.out.println(response.body()); // JSON stringTemiz, okunabilir, modern. Şimdi detaylara girelim.
HttpClient Oluşturma
HttpClient istekleri gönderen ana nesnedir. İki şekilde oluşturabilirsin:
Basit Oluşturma
HttpClient client = HttpClient.newHttpClient();Varsayılan ayarlarla bir client oluşturur: HTTP/2 tercihli, redirect takip etmez, timeout yok.
Builder ile Özelleştirme
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // HTTP/2 tercih et
.followRedirects(HttpClient.Redirect.NORMAL) // Redirect'leri takip et
.connectTimeout(Duration.ofSeconds(10)) // Bağlantı timeout'u
.executor(Executors.newFixedThreadPool(5)) // Async istekler için thread pool
.build();HttpClient thread-safe ve yeniden kullanılabilirdir. Her istek için yeni client oluşturma — uygulama boyunca tek bir instance yeterli. Bağlantı havuzlaması (connection pooling) otomatik olarak yapılır.
// Best practice: Tek client instance'ı
public class ApiClient {
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
// Bu client'ı tüm methodlarda kullan
}HttpRequest: İstekleri Oluşturma
GET İsteği
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Accept", "application/json")
.header("Authorization", "Bearer my-token")
.GET() // Varsayılan zaten GET — yazmasan da olur
.build();POST İsteği
// String body ile POST
String jsonBody = """
{
"name": "Ali",
"email": "ali@example.com",
"age": 25
}
""";
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();PUT ve DELETE
// PUT — güncelleme
HttpRequest putRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString("""
{"name": "Ali Yılmaz", "age": 26}
"""))
.build();
// DELETE — silme
HttpRequest deleteRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.DELETE()
.build();BodyPublishers
Request body farklı kaynaklardan gelebilir:
// String'den
HttpRequest.BodyPublishers.ofString("{\"key\": \"value\"}")
// Dosyadan
HttpRequest.BodyPublishers.ofFile(Path.of("data.json"))
// Byte array'den
HttpRequest.BodyPublishers.ofByteArray(bytes)
// InputStream'den (Java 16+)
HttpRequest.BodyPublishers.ofInputStream(() -> inputStream)
// Boş body
HttpRequest.BodyPublishers.noBody()Form Data Gönderme
URL-encoded form data göndermek biraz daha manuel:
String formData = "username=ali&password=secret123&remember=true";
HttpRequest formRequest = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(formData))
.build();HttpResponse: Cevapları İşleme
İstek gönderdikten sonra cevabı alırsın. Cevabın body'sini nasıl işleyeceğini BodyHandler ile belirlersin.
BodyHandlers
HttpClient client = HttpClient.newHttpClient();
// String olarak al — en yaygın kullanım
HttpResponse<String> stringResponse = client.send(request,
HttpResponse.BodyHandlers.ofString());
String body = stringResponse.body();
// Dosyaya kaydet
HttpResponse<Path> fileResponse = client.send(request,
HttpResponse.BodyHandlers.ofFile(Path.of("output.json")));
Path savedFile = fileResponse.body();
// InputStream olarak al — büyük dosyalar için
HttpResponse<InputStream> streamResponse = client.send(request,
HttpResponse.BodyHandlers.ofInputStream());
try (InputStream is = streamResponse.body()) {
// Stream'i işle
}
// Body'yi at — sadece status code lazımsa
HttpResponse<Void> discardResponse = client.send(request,
HttpResponse.BodyHandlers.discarding());
int status = discardResponse.statusCode();
// Satır satır oku — Server-Sent Events (SSE) için
HttpResponse<Stream<String>> linesResponse = client.send(request,
HttpResponse.BodyHandlers.ofLines());
linesResponse.body().forEach(System.out::println);Response Bilgileri
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// Status code
int status = response.statusCode(); // 200, 404, 500 vs.
// Headers
HttpHeaders headers = response.headers();
headers.map().forEach((key, values) ->
System.out.println(key + ": " + values));
// Belirli bir header
Optional<String> contentType = response.headers()
.firstValue("Content-Type");
// URI (redirect sonrası farklı olabilir)
URI finalUri = response.uri();
// HTTP versiyonu
HttpClient.Version version = response.version();Asenkron İstekler: sendAsync()
send() senkrondur — cevap gelene kadar thread'i bloklar. sendAsync() ise bir CompletableFuture döner — cevap geldiğinde callback çalışır.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.build();
// Asenkron gönder
CompletableFuture<HttpResponse<String>> future = client.sendAsync(
request,
HttpResponse.BodyHandlers.ofString()
);
// Cevap gelince işle
future.thenApply(HttpResponse::body)
.thenApply(body -> body.substring(0, Math.min(100, body.length())))
.thenAccept(preview -> System.out.println("Preview: " + preview))
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
// Ana thread devam edebilir...
System.out.println("Request sent, doing other work...");
// Gerekirse bekle
future.join();Paralel İstekler
Birden fazla isteği aynı anda göndermek asenkron API'nin en güçlü özelliğidir:
HttpClient client = HttpClient.newHttpClient();
List<String> urls = List.of(
"https://api.example.com/users/1",
"https://api.example.com/users/2",
"https://api.example.com/users/3"
);
// Tüm istekleri paralel gönder
List<CompletableFuture<String>> futures = urls.stream()
.map(url -> HttpRequest.newBuilder()
.uri(URI.create(url))
.build())
.map(req -> client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body))
.toList();
// Hepsini bekle ve sonuçları topla
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
futures.forEach(f -> System.out.println(f.join()));
})
.join();3 isteği sıralı göndersen 3x sürer. Paralel göndersen en yavaş olan kadar sürer. API çağrıları genellikle I/O-bound olduğundan, bu muazzam bir kazanç.
JSON Gönderme ve Alma (Jackson ile)
Gerçek dünyada JSON serileştirme/deserileştirme şart. Jackson en yaygın kütüphanedir.
Maven Dependency
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>JSON İşlemleri
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
class Main {
private static final ObjectMapper mapper = new ObjectMapper();
private static final HttpClient client = HttpClient.newHttpClient();
public static void main(String[] args) throws Exception {
// --- POST: Java nesnesi → JSON → gönder ---
record CreateUserRequest(String name, String email, int age) {}
CreateUserRequest newUser = new CreateUserRequest(
"Ali", "ali@example.com", 25
);
String jsonBody = mapper.writeValueAsString(newUser);
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> postResponse = client.send(postRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("Created: " + postResponse.statusCode());
// --- GET: JSON → Java nesnesi ---
record User(long id, String name, String email, int age) {}
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.header("Accept", "application/json")
.build();
HttpResponse<String> getResponse = client.send(getRequest,
HttpResponse.BodyHandlers.ofString());
User user = mapper.readValue(getResponse.body(), User.class);
System.out.println("Name: " + user.name());
System.out.println("Email: " + user.email());
// --- JSON array → List ---
HttpRequest listRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.build();
HttpResponse<String> listResponse = client.send(listRequest,
HttpResponse.BodyHandlers.ofString());
List<User> users = mapper.readValue(
listResponse.body(),
mapper.getTypeFactory()
.constructCollectionType(List.class, User.class)
);
users.forEach(u -> System.out.println(u.name()));
}
}💡 İpucu: ObjectMapper thread-safe ve ağır bir nesnedir — her seferinde yenisini oluşturma. Sınıf düzeyinde static final olarak tanımla.
Authentication, Headers, Timeout
Custom Headers
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Authorization", "Bearer eyJhbGciOi...")
.header("Accept", "application/json")
.header("X-Request-Id", UUID.randomUUID().toString())
.header("Accept-Language", "tr-TR")
.build();Basic Authentication
// HttpClient düzeyinde authenticator
HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"username",
"password".toCharArray()
);
}
})
.build();
// Veya manuel header ile
String credentials = Base64.getEncoder()
.encodeToString("username:password".getBytes());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/secure"))
.header("Authorization", "Basic " + credentials)
.build();Timeout Ayarları
// Client düzeyinde — tüm istekler için bağlantı timeout'u
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
// Request düzeyinde — bu istek için toplam timeout
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/slow-endpoint"))
.timeout(Duration.ofSeconds(30))
.build();
// Timeout olursa HttpTimeoutException fırlar
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
} catch (HttpTimeoutException e) {
System.err.println("Request timed out: " + e.getMessage());
}HTTP/2 Desteği
HttpClient varsayılan olarak HTTP/2'yi tercih eder. Sunucu destekliyorsa HTTP/2 kullanır, desteklemiyorsa HTTP/1.1'e düşer.
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // Varsayılan zaten bu
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// Hangi HTTP versiyonu kullanıldığını kontrol et
System.out.println("Version: " + response.version());
// HTTP_2 veya HTTP_1_1HTTP/2'nin avantajları:
Multiplexing: Tek bağlantı üzerinden birden fazla istek/cevap aynı anda
Header compression: Tekrarlanan header'lar sıkıştırılır
Server push: Sunucu istemciye proaktif olarak kaynak gönderebilir
Binary protocol: Text yerine binary — daha verimli parsing
⚠️ Dikkat: HTTP/2, HTTPS (TLS) gerektirir. Plain HTTP üzerinden HTTP/2 kullanmak nadirdir ve HttpClient bunu desteklemez. API çağrıların zaten HTTPS olmalı.
Redirect Politikaları
// NEVER — redirect takip etme (varsayılan)
HttpClient client1 = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// 301/302 dönerse, sen elle işlersin
// NORMAL — aynı protokol içinde redirect takip et
// https → https OK, https → http HAYIR
HttpClient client2 = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
// ALWAYS — her redirect'i takip et (dikkatli kullan)
HttpClient client3 = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();NORMAL çoğu senaryo için doğru seçimdir — HTTPS'den HTTP'ye düşmeyi (downgrade) önler.
Pratik Örnek: REST API Client Sınıfı
Öğrendiklerimizi birleştiren eksiksiz bir API client:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
class ApiException extends RuntimeException {
private final int statusCode;
ApiException(int statusCode, String message) {
super("HTTP " + statusCode + ": " + message);
this.statusCode = statusCode;
}
int getStatusCode() { return statusCode; }
}
class RestClient {
private final HttpClient httpClient;
private final ObjectMapper mapper;
private final String baseUrl;
private final String authToken;
RestClient(String baseUrl, String authToken) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.mapper = new ObjectMapper();
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
}
// GET — tek kaynak
<T> T get(String path, Class<T> responseType) throws Exception {
HttpRequest request = buildRequest(path)
.GET()
.build();
HttpResponse<String> response = send(request);
return mapper.readValue(response.body(), responseType);
}
// GET — liste
<T> List<T> getList(String path, Class<T> elementType) throws Exception {
HttpRequest request = buildRequest(path)
.GET()
.build();
HttpResponse<String> response = send(request);
return mapper.readValue(
response.body(),
mapper.getTypeFactory()
.constructCollectionType(List.class, elementType)
);
}
// POST — yeni kaynak oluştur
<T, R> R post(String path, T body, Class<R> responseType) throws Exception {
String jsonBody = mapper.writeValueAsString(body);
HttpRequest request = buildRequest(path)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = send(request);
return mapper.readValue(response.body(), responseType);
}
// PUT — güncelle
<T> void put(String path, T body) throws Exception {
String jsonBody = mapper.writeValueAsString(body);
HttpRequest request = buildRequest(path)
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
send(request);
}
// DELETE — sil
void delete(String path) throws Exception {
HttpRequest request = buildRequest(path)
.DELETE()
.build();
send(request);
}
private HttpRequest.Builder buildRequest(String path) {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(30));
if (authToken != null && !authToken.isEmpty()) {
builder.header("Authorization", "Bearer " + authToken);
}
return builder;
}
private HttpResponse<String> send(HttpRequest request) throws Exception {
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() >= 400) {
throw new ApiException(response.statusCode(), response.body());
}
return response;
}
}
// Kullanım
record User(Long id, String name, String email) {}
record CreateUserRequest(String name, String email) {}
class Main {
public static void main(String[] args) throws Exception {
RestClient api = new RestClient(
"https://api.example.com",
"my-auth-token"
);
// Tüm kullanıcıları getir
List<User> users = api.getList("/users", User.class);
users.forEach(u -> System.out.println(u.name()));
// Tek kullanıcı getir
User user = api.get("/users/1", User.class);
System.out.println("Found: " + user.name());
// Yeni kullanıcı oluştur
CreateUserRequest newUser = new CreateUserRequest(
"Ali", "ali@example.com"
);
User created = api.post("/users", newUser, User.class);
System.out.println("Created: " + created.id());
// Kullanıcı sil
api.delete("/users/" + created.id());
System.out.println("Deleted");
}
}Bu RestClient sınıfı production'a hazır bir başlangıç noktasıdır. Gerçek projede retry mantığı, rate limiting, circuit breaker gibi özellikler de eklersin — ama temel iskelet budur.
Hata Yönetimi
HTTP isteklerinde birçok şey ters gidebilir. Sağlam hata yönetimi şart:
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
switch (response.statusCode() / 100) {
case 2 -> System.out.println("Success: " + response.body());
case 4 -> System.err.println("Client error: " + response.statusCode());
case 5 -> System.err.println("Server error: " + response.statusCode());
}
} catch (HttpTimeoutException e) {
// Bağlantı veya okuma timeout'u
System.err.println("Timeout: " + e.getMessage());
} catch (HttpConnectTimeoutException e) {
// Bağlantı kurulamadı
System.err.println("Connection timeout: " + e.getMessage());
} catch (IOException e) {
// Ağ hatası (DNS çözümlenemedi, bağlantı koptu vs.)
System.err.println("Network error: " + e.getMessage());
} catch (InterruptedException e) {
// Thread kesildi
Thread.currentThread().interrupt();
System.err.println("Request interrupted");
}Özet
HttpClient (Java 11+),
HttpURLConnection'ın modern alternatifidir — HTTP/2, asenkron, builder pattern, temiz API.HttpClient thread-safe ve yeniden kullanılabilir — uygulama başına tek instance yeterli.
HttpRequestveHttpResponseimmutable'dır.BodyPublishers (ofString, ofFile, ofByteArray) istek body'sini, BodyHandlers (ofString, ofFile, ofInputStream) cevap body'sini işler.
sendAsync() + CompletableFuture ile asenkron istekler gönder. Paralel API çağrılarında muazzam performans kazanımı sağlar.
Jackson ile JSON serileştirme/deserileştirme yap.
ObjectMapper'ı static final olarak tanımla, her seferinde yenisini oluşturma.Production'da timeout, redirect politikası, hata yönetimi ve authentication mutlaka konfigüre et.
NORMALredirect ve 10-30 saniyelik timeout iyi başlangıç değerleridir.
AI Asistan
Sorularını yanıtlamaya hazır