Tampilan
Kenapa penting
Setiap REST API yang akan kamu bangun dengan Spring Boot, setiap webhook dari payment gateway, dan setiap panggilan ke layanan ongkir atau notifikasi berjalan di atas HTTP. Framework menyembunyikan banyak detail, tetapi saat ada masalah — client mendapat 415, pembayaran tercatat dua kali karena retry, cache menyajikan data basi, request menggantung tanpa batas waktu — yang menolongmu adalah pemahaman protokolnya. Engineer backend diharapkan bisa memilih method dan status code yang benar, membaca header, menguji endpoint dengan curl, dan memanggil API lain dari kode dengan timeout yang masuk akal.
Konsep inti
Anatomi request dan response
HTTP adalah protokol request–response tanpa state: client mengirim satu request, server membalas satu response. Request terdiri atas request line (method, path, versi), header, baris kosong, lalu body opsional. Response terdiri atas status line, header, baris kosong, dan body. Contoh nyata hasil curl -v (baris > dikirim, < diterima, dipangkas):
text
> GET /status/404 HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.14.1
> Accept: */*
>
< HTTP/2 404
< date: Fri, 23 Oct 2026 07:34:51 GMT
< content-type: text/html; charset=utf-8
< content-length: 0
< server: gunicorn/19.9.0HTTP/2 tampil dengan nama header huruf kecil dan dikirim dalam format biner, tetapi model konsepnya sama dengan HTTP/1.1. Nama header tidak peka huruf besar-kecil.
URL: path, query, dan path variable
text
https://api.toko.id:443/v1/products/KOPI-250/reviews?page=2&sort=newest
└─┬─┘ └────┬────┘ └┬┘└───────────┬──────────────┘ └────────┬────────┘
scheme host port path query stringKonvensi REST: path mengidentifikasi resource (/products/KOPI-250 — KOPI-250 adalah path variable), sedangkan query string untuk filter, sorting, dan paging (?page=2&sort=newest). Karakter khusus seperti spasi harus di-encode (%20) — jangan merangkai URL dari input user tanpa encoding.
Method dan semantiknya
| Method | Kegunaan | Safe | Idempotent | Body request |
|---|---|---|---|---|
GET | ambil resource | ya | ya | tidak |
POST | buat resource / jalankan proses | — | — | ya |
PUT | ganti resource secara utuh | — | ya | ya |
PATCH | ubah sebagian resource | — | tidak dijamin | ya |
DELETE | hapus resource | — | ya | biasanya tidak |
- Safe = tidak mengubah state server.
GETboleh di-cache, di-prefetch, dan dipanggil crawler — jangan pernah membuatGET /orders/42/cancel. - Idempotent = efek dari N request identik sama dengan efek satu request.
DELETE /orders/42dua kali tetap berakhir dengan pesanan terhapus (request kedua mungkin membalas 404, tetapi state server tidak berubah lagi).POST /paymentsdua kali bisa menagih dua kali — karena itu API pembayaran memakai headerIdempotency-Keyagar retry aman.
Status code yang wajib dikenal
| Kode | Arti | Kapan dipakai |
|---|---|---|
200 | OK | sukses dengan body |
201 | Created | resource baru dibuat; sertakan header Location |
204 | No Content | sukses tanpa body (mis. DELETE) |
301/308 | Moved Permanently / Permanent Redirect | URL pindah permanen |
302/307 | Found / Temporary Redirect | pindah sementara |
304 | Not Modified | conditional request, cache client masih valid |
400 | Bad Request | JSON rusak, validasi gagal |
401 | Unauthorized | belum/gagal autentikasi |
403 | Forbidden | terautentikasi tetapi tidak berhak |
404 | Not Found | resource tidak ada |
409 | Conflict | bentrok state, mis. stok sudah habis, email sudah terdaftar |
415 | Unsupported Media Type | Content-Type body tidak didukung |
422 | Unprocessable Content | format benar tetapi isi tidak valid secara bisnis |
429 | Too Many Requests | rate limit; sering disertai Retry-After |
500 | Internal Server Error | bug di server |
502/503/504 | Bad Gateway / Service Unavailable / Gateway Timeout | layanan di belakang bermasalah — kandidat retry |
Aturan praktis: 4xx = salah client (jangan retry tanpa memperbaiki request), 5xx = masalah server (boleh retry dengan jeda, terutama untuk method idempotent).
Header penting
| Header | Arah | Fungsi |
|---|---|---|
Content-Type | keduanya | format body yang dikirim, mis. application/json |
Accept | request | format respons yang diinginkan client |
Authorization | request | kredensial, umumnya Bearer <token> |
Cache-Control | response | aturan cache, mis. no-store atau public, max-age=60 |
ETag | response | versi resource; dipakai If-None-Match (cache) / If-Match (update) |
Location | response | URL resource baru (201) atau tujuan redirect (3xx) |
Body JSON selalu berpasangan dengan Content-Type: application/json. Data sensitif (token, data pribadi) sebaiknya dibalas dengan Cache-Control: no-store agar tidak disimpan proxy atau browser.
HTTPS
HTTPS = HTTP di atas TLS. TLS mengenkripsi seluruh request (termasuk path, query, header, dan body), memastikan integritas, dan membuktikan identitas server lewat sertifikat. Nama host tetap terlihat oleh jaringan, tetapi token di header Authorization tidak. Aturannya sederhana: semua API production wajib HTTPS — token yang dikirim lewat HTTP biasa sama dengan dibagikan ke siapa pun di jaringan yang sama. Meski terenkripsi, jangan menaruh token atau password di query string karena URL sering tercatat di log server dan riwayat browser.
curl: menguji API dari terminal
curl adalah alat wajib untuk mengecek endpoint tanpa menulis kode. Layanan publik httpbin.org memantulkan kembali request yang ia terima, cocok untuk latihan. -i menampilkan header respons:
bash
curl -i "https://httpbin.org/get?sku=KOPI-250&page=2" -H "Accept: application/json"text
HTTP/2 200
date: Fri, 23 Oct 2026 07:34:36 GMT
content-type: application/json
content-length: 329
server: gunicorn/19.9.0
access-control-allow-origin: *
access-control-allow-credentials: true
{
"args": {
"page": "2",
"sku": "KOPI-250"
},
"headers": {
"Accept": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/8.14.1",
...
},
"origin": "...",
"url": "https://httpbin.org/get?sku=KOPI-250&page=2"
}Mengirim body JSON — -X memilih method, -H menambah header, -d mengisi body:
bash
curl -i -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-d '{"sku":"KOPI-250","quantity":2}'Respons (dipangkas) menunjukkan server membaca body sebagai JSON:
text
HTTP/2 200
content-type: application/json
{
"data": "{\"sku\":\"KOPI-250\",\"quantity\":2}",
"headers": {
"Content-Length": "31",
"Content-Type": "application/json",
...
},
"json": {
"quantity": 2,
"sku": "KOPI-250"
},
...
}Beberapa perintah lain yang sering dipakai (output nyata di bawah tiap perintah):
bash
# Hanya status code
curl -s -o /dev/null -w "%{http_code}\n" https://httpbin.org/status/503
# 503
# Tanpa token → 401 + header www-authenticate: Bearer
curl -s -i https://httpbin.org/bearer
# HTTP/2 401 ... www-authenticate: Bearer
# Dengan token
curl -s https://httpbin.org/bearer -H "Authorization: Bearer token-rahasia-demo"
# { "authenticated": true, "token": "token-rahasia-demo" }
# Redirect: tanpa -L curl berhenti di 302 (location: /get); dengan -L ia mengikuti
curl -sL -o /dev/null -w "%{http_code} %{url_effective} %{num_redirects}\n" "https://httpbin.org/redirect-to?url=/get"
# 200 https://httpbin.org/get 1
# Conditional request dengan ETag
curl -s -i https://httpbin.org/etag/v1-produk-42 -H 'If-None-Match: "v1-produk-42"'
# HTTP/2 304 ... etag: v1-produk-42 (tanpa body)
# Cache-Control dari server
curl -s -i https://httpbin.org/cache/60 | grep -i cache-control
# cache-control: public, max-age=60-v menampilkan seluruh percakapan (request dan response) — langkah pertama saat men-debug integrasi.
java.net.http.HttpClient: sync dan timeout
Sejak Java 11, JDK punya HttpClient modern yang mendukung HTTP/2 tanpa library tambahan. Tiga bagian utamanya: HttpClient (dibuat sekali, thread-safe, menyimpan connection pool), HttpRequest (immutable, dibangun dengan builder), dan HttpResponse<T> yang body-nya diubah oleh BodyHandler.
java
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
public class HttpClientBasics {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5)) // batas waktu membuka koneksi
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
// GET dengan query string dan header Accept
HttpRequest get = HttpRequest.newBuilder(URI.create("https://httpbin.org/get?sku=KOPI-250"))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(10)) // batas waktu menunggu respons
.GET()
.build();
HttpResponse<String> res = client.send(get, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.version());
System.out.println(res.headers().firstValue("content-type").orElse("-"));
System.out.println(res.body().contains("\"sku\": \"KOPI-250\""));
// POST body JSON
String json = """
{"sku":"KOPI-250","quantity":2}""";
HttpRequest post = HttpRequest.newBuilder(URI.create("https://httpbin.org/post"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> created = client.send(post, HttpResponse.BodyHandlers.ofString());
System.out.println(created.statusCode() + " " + created.body().contains("\"quantity\": 2"));
// Status 4xx/5xx BUKAN exception — kamu wajib mengecek sendiri
HttpRequest notFound = HttpRequest.newBuilder(URI.create("https://httpbin.org/status/404")).build();
System.out.println(client.send(notFound, HttpResponse.BodyHandlers.discarding()).statusCode());
// Server lambat: /delay/3 menunggu 3 detik, timeout kita 1 detik
HttpRequest slow = HttpRequest.newBuilder(URI.create("https://httpbin.org/delay/3"))
.timeout(Duration.ofSeconds(1))
.build();
try {
client.send(slow, HttpResponse.BodyHandlers.ofString());
} catch (HttpTimeoutException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}Output:
text
200 HTTP_2
application/json
true
200 true
404
HttpTimeoutException: request timed outAda dua timeout yang berbeda: connectTimeout di client membatasi waktu membuka koneksi, sedangkan timeout di request membatasi waktu menunggu respons. Tanpa timeout, send bisa menunggu selamanya dan menahan thread server-mu. send melempar IOException (koneksi gagal) dan InterruptedException (thread diinterupsi) — sedangkan 404 hanyalah respons biasa.
Async dengan sendAsync
sendAsync langsung mengembalikan CompletableFuture<HttpResponse<T>> — "janji" hasil yang akan tersedia nanti — tanpa memblokir thread pemanggil. thenApply mengolah hasil saat tiba, dan join() menunggu hasil akhir. Ini berguna untuk memanggil beberapa layanan sekaligus:
java
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 java.util.concurrent.CompletableFuture;
public class HttpClientAsync {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
List<String> paths = List.of("/delay/2", "/delay/2", "/status/503");
long start = System.nanoTime();
List<CompletableFuture<String>> calls = paths.stream()
.map(path -> HttpRequest.newBuilder(URI.create("https://httpbin.org" + path))
.timeout(Duration.ofSeconds(10))
.build())
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.discarding())
.thenApply(res -> request.uri().getPath() + " -> " + res.statusCode()))
.toList();
calls.forEach(call -> System.out.println(call.join())); // tunggu hasil satu per satu
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
System.out.println("selesai < 4 detik: " + (elapsedMs < 4000));
}
}Output:
text
/delay/2 -> 200
/delay/2 -> 200
/status/503 -> 503
selesai < 4 detik: trueDua request yang masing-masing butuh 2 detik selesai bersamaan, jadi total di bawah 4 detik. CompletableFuture, thread pool, dan virtual thread dibahas mendalam di level Advanced; di Java 25 dengan virtual thread, send biasa yang memblokir pun sering sudah cukup.
Contoh di dunia backend
Service checkout memanggil API ongkos kirim pihak ketiga. Pola yang dipakai: satu HttpClient dan satu JsonMapper untuk seluruh aplikasi, header Authorization/Content-Type/Accept lengkap, timeout eksplisit, dan pemetaan status code ke exception yang bermakna. Contoh ini memakai httpbin.org/anything yang memantulkan request kita sebagai pengganti layanan asli (perlu Jackson di classpath).
java
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
record QuoteRequest(String origin, String destination, int weightGram) {}
record QuoteResult(int status, String echoedDestination, String authHeader) {}
class ShippingClient {
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
private final JsonMapper mapper = JsonMapper.builder().build();
private final String baseUrl;
private final String apiKey;
ShippingClient(String baseUrl, String apiKey) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
QuoteResult quote(String path, QuoteRequest body) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl + path))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + apiKey)
.timeout(Duration.ofSeconds(5))
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body)))
.build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status >= 500) {
throw new IllegalStateException("Layanan ongkir bermasalah (" + status + "), coba lagi nanti");
}
if (status >= 400) {
throw new IllegalArgumentException("Request ongkir ditolak (" + status + ")");
}
// httpbin /anything memantulkan request kita; layanan asli akan mengembalikan tarif
JsonNode root = mapper.readTree(response.body());
return new QuoteResult(status,
root.path("json").path("destination").asString(),
root.path("headers").path("Authorization").asString());
}
}
public class ShippingClientDemo {
public static void main(String[] args) throws IOException, InterruptedException {
var client = new ShippingClient("https://httpbin.org", "demo-key-123");
var request = new QuoteRequest("Jakarta", "Bandung", 1200);
System.out.println(client.quote("/anything/rates", request));
try {
client.quote("/status/503", request);
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
}
}Output:
text
QuoteResult[status=200, echoedDestination=Bandung, authHeader=Bearer demo-key-123]
Layanan ongkir bermasalah (503), coba lagi nantiDi aplikasi nyata, API key dibaca dari konfigurasi/environment — bukan ditulis di kode — dan error 5xx menjadi kandidat retry dengan jeda atau circuit breaker (dibahas di level Advanced).
Pertanyaan interview
1. Apa beda safe dan idempotent? Beri contoh method yang idempotent tetapi tidak safe.
Safe berarti tidak mengubah state server (GET, HEAD). Idempotent berarti mengulang request yang sama tidak menambah efek setelah yang pertama. PUT dan DELETE idempotent tetapi tidak safe: keduanya mengubah state, namun mengulanginya menghasilkan state akhir yang sama. Ini menentukan request mana yang aman di-retry otomatis.
2. Kapan memakai 400, 401, 403, 404, dan 409?
400 untuk request yang rusak atau gagal validasi. 401 bila client belum terautentikasi atau tokennya tidak valid. 403 bila identitasnya jelas tetapi tidak berhak. 404 bila resource tidak ada (kadang juga dipakai untuk menyembunyikan keberadaan resource dari yang tidak berhak). 409 bila request bentrok dengan state saat ini, misalnya email sudah terdaftar atau versi data sudah berubah.
3. Apa yang terjadi bila memanggil layanan eksternal tanpa timeout?
Bila layanan itu lambat atau menggantung, thread pemanggil ikut menunggu tanpa batas. Di server web, thread yang tertahan menumpuk sampai pool habis dan seluruh aplikasi ikut tidak merespons — gangguan satu dependency menjalar menjadi outage. Selalu set connect timeout dan request timeout, lalu tangani HttpTimeoutException.
Jebakan umum
- Mengira status 4xx/5xx melempar exception di
HttpClient. Selalu cekstatusCode()sebelum memakai body. - Lupa
Content-Type: application/json.curl -ddefault-nya form-urlencoded; server JSON membalas 415 atau gagal parse. - Tidak memasang timeout. Default
HttpClienttidak punya request timeout — thread bisa menunggu selamanya. - Membuat
HttpClientbaru per request. Connection pool tidak terpakai ulang; buat sekali dan bagikan. - Memakai
GETuntuk operasi yang mengubah data atau me-retryPOSTpembayaran tanpaIdempotency-Key. - Menaruh token di query string atau di log. URL tercatat di banyak tempat; pakai header
Authorization.
Ringkasan
- HTTP adalah request–response tanpa state: request line/status line, header, baris kosong, body.
- Path mengidentifikasi resource, query string untuk filter dan paging;
GETsafe,PUT/DELETEidempotent,POSTtidak. - 2xx sukses, 3xx redirect/cache, 4xx salah client (jangan retry buta), 5xx masalah server (kandidat retry).
Content-Type/Acceptuntuk format,Authorizationuntuk kredensial,Cache-Control/ETaguntuk cache; produksi wajib HTTPS.curl -i,-v,-X,-H,-d,-Lcukup untuk menguji hampir semua endpoint.HttpClientdibuat sekali, selalu dengan timeout;senduntuk sync,sendAsyncuntuk paralel.