Instagram API / Code samples / Java

Instagram API in Java — HttpClient From the JDK, No HTTP Dependency

java.net.http.HttpClient has shipped with the JDK since Java 11, so the HTTP side needs nothing added. The JDK has no JSON parser, which is the one dependency these samples do take.

First call

Calling the Instagram API from Java.

Copy this, set your key, and you have a working integration. Everything below is the same request with more of the edge cases handled.

Install

com.fasterxml.jackson.core:jackson-databind

Quickstart

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

public class Quickstart {

    private static final String BASE = "https://api.socialscrape.dev/v1";
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static void main(String[] args) throws Exception {
        String key = System.getenv("INSCRAPE_KEY");
        if (key == null || key.isBlank()) {
            throw new IllegalStateException("INSCRAPE_KEY is not set");
        }

        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();

        String handle = URLEncoder.encode("natgeo", StandardCharsets.UTF_8);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE + "/instagram/profile?handle=" + handle))
                .header("x-api-key", key)
                .timeout(Duration.ofSeconds(30))
                .GET()
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        JsonNode body = MAPPER.readTree(response.body());

        if (response.statusCode() != 200) {
            JsonNode error = body.get("error");
            throw new IllegalStateException(response.statusCode() + " "
                    + error.get("code").asText() + ": " + error.get("message").asText());
        }

        JsonNode data = body.get("data");
        System.out.println(data.get("handle").asText() + " " + data.get("follower_count").asLong());
        System.out.println("charged " + body.get("credits_charged").asInt()
                + " remaining " + body.get("credits_remaining").asInt()
                + " requested " + body.get("requested_at").asText());
    }
}

Pagination

Java cursor pagination for Instagram API lists.

Every list endpoint returns a cursor. Stop when it is absent, not after a fixed number of pages — the page size is not a contract.

Java — cursor loop

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

public class Paginate {

    private static final String BASE = "https://api.socialscrape.dev/v1";
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final HttpClient CLIENT = HttpClient.newHttpClient();

    private static String encode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }

    private static JsonNode getPage(String handle, String cursor) throws Exception {
        StringBuilder url = new StringBuilder(BASE)
                .append("/instagram/posts?handle=").append(encode(handle))
                .append("&limit=50");

        if (cursor != null) {
            url.append("&cursor=").append(encode(cursor));
        }

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url.toString()))
                .header("x-api-key", System.getenv("INSCRAPE_KEY"))
                .timeout(Duration.ofSeconds(30))
                .GET()
                .build();

        HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
        JsonNode body = MAPPER.readTree(response.body());

        if (response.statusCode() != 200) {
            throw new IllegalStateException(response.statusCode() + " "
                    + body.get("error").get("code").asText());
        }

        return body;
    }

    public static void main(String[] args) throws Exception {
        List<JsonNode> posts = new ArrayList<>();
        String cursor = null;
        int spent = 0;

        for (int page = 0; page < 10; page++) {
            JsonNode body = getPage("natgeo", cursor);
            spent += body.get("credits_charged").asInt();
            body.get("data").get("posts").forEach(posts::add);

            JsonNode next = body.get("data").get("next_cursor");
            cursor = (next == null || next.isNull()) ? null : next.asText();

            if (cursor == null || cursor.isEmpty()) {
                break;
            }
        }

        System.out.println(posts.size() + " posts across " + spent + " credits");
    }
}

Errors

Java error handling for the Instagram API.

Only HTTP 200 is charged, so a retry after a 500 costs you nothing except time. A retry after a 400 costs you nothing and fixes nothing.

Java — error handling

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

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 Errors {

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final HttpClient CLIENT = HttpClient.newHttpClient();

    static final class ApiException extends RuntimeException {
        final int status;
        final String code;

        ApiException(int status, String code, String message) {
            super(status + " " + code + ": " + message);
            this.status = status;
            this.code = code;
        }

        boolean retryable() {
            return status >= 500;
        }
    }

    static JsonNode call(String url) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("x-api-key", System.getenv("INSCRAPE_KEY"))
                .timeout(Duration.ofSeconds(30))
                .GET()
                .build();

        HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
        JsonNode body = MAPPER.readTree(response.body());

        if (response.statusCode() == 200) {
            return body;
        }

        JsonNode error = body.get("error");
        throw new ApiException(response.statusCode(),
                error.get("code").asText(), error.get("message").asText());
    }

    static JsonNode callWithRetry(String url, int attempts) throws Exception {
        for (int attempt = 0; ; attempt++) {
            try {
                return call(url);
            } catch (ApiException e) {
                if (!e.retryable() || attempt == attempts - 1) {
                    throw e;
                }
                Thread.sleep(1000L << attempt);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        String url = "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo";

        try {
            JsonNode body = callWithRetry(url, 3);
            JsonNode data = body.path("data");
            if (data.path("profile").isNull()) {
                System.out.println("account not found; credits charged: " + body.path("credits_charged"));
            } else {
                System.out.println(data.path("profile").path("follower_count").asLong()
                        + " credits left: " + body.path("credits_remaining"));
            }
        } catch (ApiException e) {
            switch (e.code) {
                case "ENDPOINT_NOT_FOUND" -> System.out.println("API route not found");
                case "INSUFFICIENT_CREDITS" -> System.out.println("out of credits");
                case "INVALID_API_KEY" -> System.out.println("check INSCRAPE_KEY");
                default -> throw e;
            }
        }
    }
}

Gotchas

Things that only bite in Java.

The JDK ships an HTTP client but not a JSON parser

HttpClient replaces OkHttp and Apache HttpClient entirely. Jackson or Gson is still needed to read the body, and that is the only dependency in these samples. Gson works identically if it is already on your classpath.

URL-encode the cursor

Java has no url.Values equivalent, so string concatenation is on you. Cursors contain characters that must be percent-encoded; skipping URLEncoder gives you a 400 INVALID_PARAMS on the second page and a confusing afternoon.

Two timeouts, not one

connectTimeout on the client covers establishing the connection; timeout on the request covers the whole exchange. Only the second one saves you when the upstream is slow rather than unreachable.

Spring Boot: RestClient works the same way

restClient.get().uri(...).header("x-api-key", key).retrieve().body(JsonNode.class) is equivalent. Use onStatus to keep the error body rather than letting the default handler throw it away, because that body says whether you were charged.

FAQ

Instagram API in Java FAQ.

Which Java version do I need?

Java 11 for HttpClient. The switch expressions in the error sample need Java 14, and rewriting them as an if chain drops the requirement back to 11.

Is there a Maven artifact for an Instagram API?

Not from us, and the ones on Maven Central that scrape Instagram directly break whenever the site changes. A REST call plus Jackson is the version that keeps working.

How do I map responses to POJOs?

Generate them from https://socialscrape.dev/openapi.json, or write small records per endpoint and use MAPPER.treeToValue(body.get("data"), Profile.class). The envelope is identical for all sixteen endpoints, so only the data type varies.

Does it work in Android?

java.net.http.HttpClient is not available across the Android versions most apps still support, so use OkHttp for the transport there. Either way, keep the key on your own backend rather than in the app package.

Can I fetch a user's email address or phone number?

No. We return only what a public profile shows. Contact fields are not part of any response, and no endpoint exposes follower emails.

Other languages

Instagram API examples beyond Java.

Every endpoint these samples call is documented at /docs/instagram.