APIs & HTTP

A web API answers HTTP requests with structured data, usually JSON.

Method Purpose Status Meaning
GET read 2xx success
POST create 3xx redirect
PUT/PATCH update 4xx your mistake (404, 401, 429)
DELETE delete 5xx server’s mistake

The built-in HTTP client

import java.net.URI;
import java.net.http.*;

void main() throws Exception {
    try (var client = HttpClient.newHttpClient()) {

        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.github.com/repos/openjdk/jdk"))
            .header("Accept", "application/json")
            .GET()
            .build();

        var response = client.send(request, HttpResponse.BodyHandlers.ofString());

        IO.println(response.statusCode());
        IO.println(response.body());
    }
}

Configuration:

var client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(10))
    .followRedirects(HttpClient.Redirect.NORMAL)
    .build();

var request = HttpRequest.newBuilder(URI.create(url))
    .timeout(Duration.ofSeconds(20))
    .header("User-Agent", "java-course/1.0")
    .build();

POST with a body:

var body = """
    {"title": "New task", "done": false}
    """;

HttpRequest.newBuilder()
    .uri(URI.create("https://example.test/api/tasks"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
Warning

A 404 does not throw. Check response.statusCode() yourself.

if (response.statusCode() >= 400) {
    throw new IllegalStateException("API error %d".formatted(response.statusCode()));
}

JSON

Java has no JSON parser in the standard library. Use Jackson or Gson:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.18.2</version>
</dependency>
@JsonIgnoreProperties(ignoreUnknown = true)
record Repository(String name, String description, int stargazers_count) {}

var mapper = new ObjectMapper();
var repo = mapper.readValue(response.body(), Repository.class);
var json = mapper.writeValueAsString(repo);

Full example

void main() throws Exception {
    var zip = IO.readln("ZIP code: ").strip();

    try (var client = HttpClient.newHttpClient()) {
        var request = HttpRequest.newBuilder(
            URI.create("https://api.zippopotam.us/us/" + zip)).build();

        var response = client.send(request, HttpResponse.BodyHandlers.ofString());

        switch (response.statusCode()) {
            case 200 -> IO.println(response.body());
            case 404 -> IO.println("unknown ZIP code");
            default  -> IO.println("unexpected status " + response.statusCode());
        }
    }
}

Manners

  • respect rate limits — on 429, back off instead of retrying immediately
  • always set timeouts
  • keys go in environment variables: System.getenv("API_TOKEN")
  • one HttpClient per application, not per request

★ Exercises

  1. Call any public API; print the status code and the first 200 characters.
  2. Print all response headers (response.headers().map()).
  3. Fetch three URLs one after another and measure the total time — you will parallelise this in chapter 15.
  4. Retry up to three times with growing delays when you get a 429.
  5. Read a token from an environment variable and send it as an Authorization header. What should happen if the variable is missing?