Skip to content

Java (HttpClient)

java.net.http.HttpClient sync and async usage, plus Jackson for JSON binding.

Java 11 added java.net.http.HttpClient, a built-in HTTP/2 client that makes Apache HttpClient and OkHttp optional. Combined with Jackson for JSON binding, it covers everything you need to call the GoRest API. This guide uses HttpClient for the core examples, then shows the Spring WebClient equivalent for reactive applications.

Setup

Define one HttpClient per service; reuse it across calls. The connect timeout is the time to establish the TCP connection; the request timeout (set per-request below) is the total time including the response.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class GoRest {
    private static final String API   = "https://gorest.co.in/public/v2";
    private static final String TOKEN = System.getenv("GOREST_TOKEN");

    private final HttpClient http = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();
}

GET requests

The fluent builder API matches the shape of an HTTP request: uri, header one or more times, then a verb method (.GET(), .POST(...), etc.).

public HttpResponse<String> get(String path) throws Exception {
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(API + path))
        .header("Authorization", "Bearer " + TOKEN)
        .header("Accept", "application/json")
        .timeout(Duration.ofSeconds(15))
        .GET()
        .build();
    return http.send(req, HttpResponse.BodyHandlers.ofString());
}

POST with Jackson

Add Jackson for JSON serialization (com.fasterxml.jackson.databind). Marshal your DTO, send the JSON string, then unmarshal the response.

import com.fasterxml.jackson.databind.ObjectMapper;

private final ObjectMapper json = new ObjectMapper();

public HttpResponse<String> post(String path, Object body) throws Exception {
    String payload = json.writeValueAsString(body);
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(API + path))
        .header("Authorization", "Bearer " + TOKEN)
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .timeout(Duration.ofSeconds(15))
        .POST(HttpRequest.BodyPublishers.ofString(payload))
        .build();
    return http.send(req, HttpResponse.BodyHandlers.ofString());
}

Records and typed responses

Java 17+ records make response DTOs short. Use TypeFactory to deserialize into a parameterized list.

public record User(int id, String name, String email,
                   String gender, String status) {}

public List<User> listUsers(String status) throws Exception {
    HttpResponse<String> res = get("/users?status=" + status);
    if (res.statusCode() != 200) {
        throw new RuntimeException("List -> " + res.statusCode());
    }
    return json.readValue(res.body(),
        json.getTypeFactory().constructCollectionType(List.class, User.class));
}

Async

sendAsync returns a CompletableFuture for non-blocking work. Useful in servers that want to fan out reads without blocking a request thread.

public CompletableFuture<List<User>> listUsersAsync(String status) {
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(API + "/users?status=" + status))
        .header("Authorization", "Bearer " + TOKEN)
        .header("Accept", "application/json")
        .GET()
        .build();
    return http.sendAsync(req, HttpResponse.BodyHandlers.ofString())
        .thenApply(res -> {
            if (res.statusCode() != 200) {
                throw new RuntimeException("List -> " + res.statusCode());
            }
            try {
                return json.readValue(res.body(),
                    json.getTypeFactory().constructCollectionType(List.class, User.class));
            } catch (Exception e) { throw new RuntimeException(e); }
        });
}

Retry on rate limits

Wrap your send call to read X-RateLimit-Reset on a 429 and sleep before retrying:

public HttpResponse<String> sendWithRetry(HttpRequest req) throws Exception {
    for (int attempt = 0; attempt < 3; attempt++) {
        HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
        if (res.statusCode() != 429) return res;
        int wait = res.headers().firstValue("X-RateLimit-Reset")
            .map(Integer::parseInt).orElse(1);
        Thread.sleep(Math.max(wait, 1) * 1000L);
    }
    throw new RuntimeException("Rate limited; gave up");
}

Spring WebClient (advanced)

If your application is already on Spring Boot, use WebClient instead of HttpClient. It integrates with the rest of the framework (config, metrics, tracing) and supports reactive types. This section assumes Spring Boot, dependency injection, and reactive Mono / Flux types; skip it if you are new to Spring and stick with HttpClient above.

import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class GoRestConfig {
    @Bean
    public WebClient goRestClient(@Value("${gorest.token}") String token) {
        return WebClient.builder()
            .baseUrl("https://gorest.co.in/public/v2")
            .defaultHeader("Authorization", "Bearer " + token)
            .defaultHeader("Accept", "application/json")
            .build();
    }
}

@Service
public class UserService {
    private final WebClient client;
    public UserService(WebClient goRestClient) { this.client = goRestClient; }

    public Mono<List<User>> listActive() {
        return client.get()
            .uri("/users?status=active")
            .retrieve()
            .bodyToFlux(User.class)
            .collectList();
    }
}

Tips

  • HttpClient is thread-safe. Create one, @Inject it everywhere.
  • Use BodyHandlers.ofInputStream() if you want to stream a response rather than buffer it.
  • In Maven, Jackson lives at com.fasterxml.jackson.core:jackson-databind. In Gradle: implementation 'com.fasterxml.jackson.core:jackson-databind'.
  • When deserializing into records with snake_case JSON, configure ObjectMapper with .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE). The API does not use snake_case, but get into the habit before you hit one that does.
Related guides

Keep going