Skip to content

Java guide

Solve captchas from Java with one function

One dependency-free class on the built-in HttpClient creates a task, polls for the result and returns the text or token. It even brings its own tiny JSON parser, so there is nothing to add to your build.

  • Java 17+
  • java.net.http
  • No dependencies
  • No SDK

Setup

Before you start

  • JDK 17 or newer (for java.net.http.HttpClient, records and pattern matching)
  • No libraries: the helper ships a small JSON reader and writer
  • A bypass.so API key (create one) with a positive balance

Install

terminal
java -version
mkdir bypass-demo && cd bypass-demo

Store your key

The examples read your key from the BYPASS_API_KEY environment variable, so it never lands in source control.

Shell
export BYPASS_API_KEY="your-api-key"

Check that the key works: curl -H "Authorization: Bearer $BYPASS_API_KEY" https://bypass.so/balance returns your balance.

Step 1

Add the helper

Save this file next to your code. It wraps the whole task lifecycle in one call, Bypass.solve(type, params), so the rest of your project never deals with polling.

  • createTask: sends your key, the task type and its fields as one flat JSON object.
  • Fast path: if the answer is already ready, it returns at once.
  • Polling: otherwise it checks /getTaskResult/{taskId} every two seconds until the result is ready or the timeout runs out.
  • Errors: any reply with a non-zero errorId, including a failed task, becomes an error carrying the errorCode, so you can branch on it.
  • taskId: kept with the solution, so you can report a wrong answer later.
Bypass.java
import java.io.IOException;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/** bypass.so client: Java 17+, no dependencies. */
public final class Bypass {
    public static final String API_URL = "https://bypass.so";
    private static final HttpClient HTTP = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    private Bypass() {}

    /** Any API reply with errorId != 0. */
    public static final class BypassException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public final String code;
        public final String taskId;

        public BypassException(String code, String description, String taskId) {
            super(code + ": " + description);
            this.code = code;
            this.taskId = taskId;
        }
    }

    private static String key() {
        String key = System.getenv("BYPASS_API_KEY");
        return key == null ? "" : key;
    }

    public static Map<String, Object> solve(String type, Map<String, ?> params) throws InterruptedException {
        return solve(type, params, Duration.ofSeconds(120));
    }

    /** Creates a task and polls every 2 seconds until it is ready. Returns the solution plus taskId. */
    public static Map<String, Object> solve(String type, Map<String, ?> params, Duration timeout) throws InterruptedException {
        String key = key();
        if (key.isEmpty()) {
            throw new BypassException("ERROR_KEY_INVALID", "set the BYPASS_API_KEY environment variable", null);
        }
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("clientKey", key);
        body.put("type", type);
        body.putAll(params);

        Map<String, Object> data = call(request("/createTask")
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(Json.write(body))));
        String taskId = (String) data.get("taskId");
        long deadline = System.nanoTime() + timeout.toNanos();

        while (!"ready".equals(data.get("status"))) {
            if ("failed".equals(data.get("status"))) {
                throw new BypassException("ERROR_CAPTCHA_UNSOLVABLE", "task failed", taskId);
            }
            if (System.nanoTime() > deadline) {
                throw new BypassException("CLIENT_TIMEOUT", "task " + taskId + " was not ready after " + timeout.toSeconds() + "s", taskId);
            }
            Thread.sleep(2_000);
            data = call(request("/getTaskResult/" + enc(taskId) + "?clientKey=" + enc(key)).GET());
        }
        Map<String, Object> solution = new LinkedHashMap<>(asMap(data.get("solution")));
        solution.put("taskId", taskId);
        return solution;
    }

    /** The token for widget types, or the text for recognition types. */
    public static String answer(Map<String, Object> solution) {
        for (String field : List.of("gRecaptchaResponse", "token", "text")) {
            if (solution.get(field) instanceof String value && !value.isEmpty()) {
                return value;
            }
        }
        return null;
    }

    public static Map<String, Object> balance() throws InterruptedException {
        return call(request("/balance").header("Authorization", "Bearer " + key()).GET());
    }

    public static Map<String, Object> reportBad(String taskId) throws InterruptedException {
        return call(request("/reportBad?task_id=" + enc(taskId))
                .header("Authorization", "Bearer " + key())
                .POST(HttpRequest.BodyPublishers.noBody()));
    }

    /** Encodes fields as application/x-www-form-urlencoded, for posting a token to your own form. */
    public static String form(Map<String, String> fields) {
        return fields.entrySet().stream()
                .map(e -> enc(e.getKey()) + "=" + enc(e.getValue()))
                .collect(Collectors.joining("&"));
    }

    private static HttpRequest.Builder request(String path) {
        return HttpRequest.newBuilder(URI.create(API_URL + path)).timeout(Duration.ofSeconds(30));
    }

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

    private static Map<String, Object> call(HttpRequest.Builder req) throws InterruptedException {
        HttpResponse<String> res;
        try {
            res = HTTP.send(req.build(), HttpResponse.BodyHandlers.ofString());
        } catch (IOException e) {
            throw new BypassException("HTTP_ERROR", e.toString(), null);
        }
        Object parsed;
        try {
            parsed = Json.parse(res.body());
        } catch (IllegalArgumentException e) {
            parsed = null;
        }
        if (!(parsed instanceof Map)) {
            throw new BypassException("HTTP_ERROR", "HTTP " + res.statusCode() + " without a JSON body", null);
        }
        Map<String, Object> data = asMap(parsed);
        if (data.get("errorId") instanceof Number id && id.intValue() != 0) {
            Object code = data.getOrDefault("errorCode", "ERROR_UNKNOWN");
            Object description = data.getOrDefault("errorDescription", "");
            throw new BypassException(String.valueOf(code), String.valueOf(description), (String) data.get("taskId"));
        }
        return data;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> asMap(Object value) {
        return value instanceof Map ? (Map<String, Object>) value : Map.of();
    }

    /** Just enough JSON: objects, arrays, strings, numbers, booleans and null. */
    static final class Json {
        private final String s;
        private int i;

        private Json(String s) {
            this.s = s;
        }

        static Object parse(String text) {
            Json p = new Json(text);
            p.ws();
            Object value = p.value();
            p.ws();
            if (p.i != p.s.length()) {
                throw p.error();
            }
            return value;
        }

        static String write(Object value) {
            StringBuilder b = new StringBuilder();
            write(b, value);
            return b.toString();
        }

        private static void write(StringBuilder b, Object v) {
            if (v == null) {
                b.append("null");
            } else if (v instanceof Number || v instanceof Boolean) {
                b.append(v);
            } else if (v instanceof Map<?, ?> m) {
                b.append('{');
                String sep = "";
                for (Map.Entry<?, ?> e : m.entrySet()) {
                    b.append(sep);
                    quote(b, String.valueOf(e.getKey()));
                    b.append(':');
                    write(b, e.getValue());
                    sep = ",";
                }
                b.append('}');
            } else if (v instanceof Iterable<?> list) {
                b.append('[');
                String sep = "";
                for (Object item : list) {
                    b.append(sep);
                    write(b, item);
                    sep = ",";
                }
                b.append(']');
            } else {
                quote(b, v.toString());
            }
        }

        private static void quote(StringBuilder b, String str) {
            b.append('"');
            for (char c : str.toCharArray()) {
                switch (c) {
                    case '"' -> b.append("\\\"");
                    case '\\' -> b.append("\\\\");
                    case '\n' -> b.append("\\n");
                    case '\r' -> b.append("\\r");
                    case '\t' -> b.append("\\t");
                    default -> {
                        if (c < 0x20) {
                            b.append(String.format("\\u%04x", (int) c));
                        } else {
                            b.append(c);
                        }
                    }
                }
            }
            b.append('"');
        }

        private IllegalArgumentException error() {
            return new IllegalArgumentException("invalid JSON at offset " + i);
        }

        private void ws() {
            while (i < s.length() && " \t\r\n".indexOf(s.charAt(i)) >= 0) {
                i++;
            }
        }

        private boolean peek(char c) {
            return i < s.length() && s.charAt(i) == c;
        }

        private void expect(char c) {
            if (!peek(c)) {
                throw error();
            }
            i++;
        }

        private Object value() {
            if (i >= s.length()) {
                throw error();
            }
            return switch (s.charAt(i)) {
                case '{' -> object();
                case '[' -> array();
                case '"' -> string();
                case 't' -> literal("true", Boolean.TRUE);
                case 'f' -> literal("false", Boolean.FALSE);
                case 'n' -> literal("null", null);
                default -> number();
            };
        }

        private Object literal(String word, Object value) {
            if (!s.startsWith(word, i)) {
                throw error();
            }
            i += word.length();
            return value;
        }

        private Map<String, Object> object() {
            Map<String, Object> map = new LinkedHashMap<>();
            expect('{');
            ws();
            if (peek('}')) {
                i++;
                return map;
            }
            while (true) {
                ws();
                String key = string();
                ws();
                expect(':');
                ws();
                map.put(key, value());
                ws();
                if (peek(',')) {
                    i++;
                } else {
                    expect('}');
                    return map;
                }
            }
        }

        private List<Object> array() {
            List<Object> list = new ArrayList<>();
            expect('[');
            ws();
            if (peek(']')) {
                i++;
                return list;
            }
            while (true) {
                ws();
                list.add(value());
                ws();
                if (peek(',')) {
                    i++;
                } else {
                    expect(']');
                    return list;
                }
            }
        }

        private String string() {
            expect('"');
            StringBuilder b = new StringBuilder();
            while (true) {
                if (i >= s.length()) {
                    throw error();
                }
                char c = s.charAt(i++);
                if (c == '"') {
                    return b.toString();
                }
                if (c != '\\') {
                    b.append(c);
                    continue;
                }
                if (i >= s.length()) {
                    throw error();
                }
                char e = s.charAt(i++);
                switch (e) {
                    case 'n' -> b.append('\n');
                    case 't' -> b.append('\t');
                    case 'r' -> b.append('\r');
                    case 'b' -> b.append('\b');
                    case 'f' -> b.append('\f');
                    case 'u' -> {
                        if (i + 4 > s.length()) {
                            throw error();
                        }
                        b.append((char) Integer.parseInt(s.substring(i, i + 4), 16));
                        i += 4;
                    }
                    default -> b.append(e);
                }
            }
        }

        private Number number() {
            int start = i;
            while (i < s.length() && "+-0123456789.eE".indexOf(s.charAt(i)) >= 0) {
                i++;
            }
            String t = s.substring(start, i);
            try {
                if (t.matches("-?\\d+")) {
                    return Long.valueOf(t);
                }
                return Double.valueOf(t);
            } catch (NumberFormatException e) {
                throw error();
            }
        }
    }
}

Good to know

Already using Jackson or Gson? Replace Json.parse and Json.write with your library’s calls. Nothing else in the helper changes.

Project layout used below:

files
bypass-demo/
  Bypass.java      the helper (step 1)
  SolveMath.java   your program (step 2)

Step 2

Solve any supported type

Every task type uses the same call. Only the type name and its fields change. Pick a tab to see a complete, runnable file, then run it with javac *.java && java SolveMath (rename to match the tab). Tabs marked soon are ready for launch day, but those types are not live yet.

SolveMath.java
import java.util.Map;

public class SolveMath {
    public static void main(String[] args) throws Exception {
        Map<String, Object> solution = Bypass.solve("MathCaptchaTask", Map.of("captchaText", "12 + 30 = ?"));
        System.out.println(solution.get("text"));
    }
}
run it
javac *.java
java SolveMath

reCAPTCHA v3 and hCaptcha are coming soon

RecaptchaV3Task and HCaptchaTask are not live yet. Until launch, createTask returns ERROR_TASK_NOT_SUPPORTED and nothing is charged. Get notified when they ship.

Token types and your form

For reCAPTCHA, Turnstile and (once live) hCaptcha the token goes into the same form field the widget would fill: g-recaptcha-response, h-captcha-response or cf-turnstile-response. Send the exact URL of the page that shows the widget as websiteURL.

Step 3

Handle errors and retries

Some failures are worth a second try and some are not. A missing balance or a wrong key will fail again, so stop and fix it. An unsolvable task, a timeout or a network hiccup is worth one or two more attempts with a short pause. Bypass.BypassException is unchecked and carries the API code in e.code. Network failures surface as HTTP_ERROR, so one set covers both.

SolveWithRetry.java
import java.util.Map;
import java.util.Set;

public class SolveWithRetry {
    static final Set<String> RETRYABLE = Set.of("ERROR_CAPTCHA_UNSOLVABLE", "ERROR_INTERNAL", "HTTP_ERROR", "CLIENT_TIMEOUT");

    static Map<String, Object> solveWithRetry(String type, Map<String, ?> params, int attempts) throws InterruptedException {
        for (int attempt = 1; ; attempt++) {
            try {
                return Bypass.solve(type, params);
            } catch (Bypass.BypassException e) {
                if (!RETRYABLE.contains(e.code) || attempt >= attempts) {
                    throw e;
                }
                System.err.println("attempt " + attempt + ": " + e.code + ", retrying");
                Thread.sleep(2_000L * attempt);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> solution = solveWithRetry("MathCaptchaTask", Map.of("captchaText", "12 + 30 = ?"), 3);
        System.out.println(solution.get("text"));
    }
}

Failed tasks cost nothing

The task price is held when you create a task and released if it fails. You are only charged when the result is ready.

Step 4

Check your balance and flag wrong answers

GET /balance and POST /reportBad use your key as a Bearer token. Report a task only when the target site actually rejected its answer. The refund is applied once per task, and the task is closed afterwards.

Account.java
import java.util.Map;

public class Account {
    static void flagWrongAnswer(Map<String, Object> solution) throws InterruptedException {
        Map<String, Object> result = Bypass.reportBad((String) solution.get("taskId"));
        System.out.println("refunded units: " + result.get("refunded") + " balance now: " + result.get("balanceUsd"));
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> info = Bypass.balance();
        System.out.printf("balance: $%.2f%n", ((Number) info.get("balanceUsd")).doubleValue());
    }
}

Scale

Solve many tasks in parallel

Each task is independent, so throughput grows with concurrency. A fixed thread pool of eight keeps eight tasks in flight. The shared HttpClient inside the helper is thread-safe, so every worker can call Bypass.solve directly. Start with a limit of 8 and adjust while you watch error rates and spend.

Batch.java
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Batch {
    public static void main(String[] args) throws Exception {
        List<Path> files = new ArrayList<>();
        try (DirectoryStream<Path> dir = Files.newDirectoryStream(Path.of("captchas"), "*.png")) {
            dir.forEach(files::add);
        }
        ExecutorService pool = Executors.newFixedThreadPool(8); // at most 8 tasks in flight
        try {
            List<Future<String>> jobs = new ArrayList<>();
            for (Path path : files) {
                jobs.add(pool.submit(() -> {
                    String imageBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(path));
                    Map<String, Object> solution = Bypass.solve("ImageToTextTask", Map.of("imageBase64", imageBase64));
                    return path.getFileName() + ": " + solution.get("text");
                }));
            }
            for (Future<String> job : jobs) {
                try {
                    System.out.println(job.get());
                } catch (Exception e) {
                    System.out.println("failed: " + e.getCause());
                }
            }
        } finally {
            pool.shutdown();
        }
    }
}

Reference

Task type reference

What each type expects and returns. Prices are per 1,000 solved tasks; larger top-ups add bonus credit (see pricing).

Task type reference
Task typeMathCaptchaTaskYou sendcaptchaTextYou gettextPrice / 1k$0.35
Task typeImageToTextTaskYou sendimageBase64You gettextPrice / 1k$0.28
Task typeAudioCaptchaTaskYou sendaudioBase64You gettextPrice / 1k$0.35
Task typeRecaptchaV2TaskYou sendwebsiteURLwebsiteKeyYou getgRecaptchaResponse and token, plus tokenFieldPrice / 1k$0.56
Task typeTurnstileTaskYou sendwebsiteURLwebsiteKeyYou gettoken, plus tokenFieldPrice / 1k$0.84
Task typeRecaptchaV3TaskComing soonYou sendwebsiteURLwebsiteKeyactionYou getgRecaptchaResponse and token, plus tokenFieldPrice / 1kAt launch
Task typeHCaptchaTaskComing soonYou sendwebsiteURLwebsiteKeyYou gettoken, plus tokenFieldPrice / 1kAt launch

Errors

Troubleshooting

Errors and fixes
ErrorERROR_KEY_INVALIDSourceHTTP 403What it meansThe key is missing, mistyped or revoked.How to fix itCheck that BYPASS_API_KEY is set in the same shell that runs your code, with no quotes or spaces around it.
ErrorERROR_INSUFFICIENT_CREDITSSourceHTTP 402What it meansYour balance cannot cover the task price.How to fix itTop up from the dashboard. Nothing was charged for this request.
ErrorERROR_TASK_NOT_SUPPORTEDSourceHTTP 200What it meansThe type string is unknown, or the type is still coming soon (reCAPTCHA v3, hCaptcha).How to fix itCopy the exact name, for example TurnstileTask. Names are case-sensitive. Nothing is charged.
ErrorERROR_BAD_REQUESTSourceHTTP 400What it meansThe body is not valid JSON, or a field has the wrong type.How to fix itSend Content-Type: application/json and string values for clientKey, type and the task fields.
ErrorERROR_CAPTCHA_UNSOLVABLESourceHTTP 200What it meansThis task could not be completed.How to fix itYou are not charged. Check the image, sitekey and page URL, then submit a new task.
ErrorERROR_NOT_FOUNDSourceHTTP 404What it meansThe taskId is unknown or belongs to another key.How to fix itPoll with the taskId returned by your own createTask call, using the same key.
ErrorERROR_INTERNALSourceHTTP 500What it meansSomething went wrong on our side.How to fix itRetry after a short pause. The helper’s retry wrapper does this for you.
ErrorHelper timeoutSourceyour codeWhat it meansNo result inside your timeout.How to fix itToken types usually need several seconds. Keep at least 60–120 s and retry with a new task.
ErrorHTTP_ERRORSourceyour codeWhat it meansA proxy or network layer answered without JSON.How to fix itRetry with backoff. If it persists, check that your code calls https://bypass.so.

Every error body has the same shape: {"errorId":1,"errorCode":"…","errorDescription":"…"}. Full list in the API reference.

Production

Best practices

  • Keep the key out of code

    Read it from an environment variable or your secret store. Never commit it or ship it to a web page.

  • Solve right before submit

    Tokens are short-lived. Fill the form first, then request the token and submit at once.

  • Cap your concurrency

    Start with 5–10 parallel tasks and raise it while you watch error rates and spend.

  • Log every taskId

    It is the only thing you need to debug a result or report a wrong answer.

  • Match the page exactly

    Send the URL where the widget renders and the sitekey from that page, not from a different form.

  • Report wrong answers

    If a site rejects a solved result, call reportBad with its taskId. The price returns to your balance once.

FAQ

Questions

No. The API is two HTTPS endpoints that take and return JSON. The helper on this page is a single file that you own, can read in a few minutes and can change freely.

Math, image and audio tasks often finish while the createTask request is still open. The response then already has "status": "ready" and a solution, so the helper returns without polling. Token types always come back as processing first.

Every two seconds is a good default. Polling faster does not make a task finish sooner. Keep the overall timeout at 60 to 120 seconds for token types.

No. You pay only when a task comes back ready. If a site rejects a solved answer, call reportBad with its taskId and the price returns to your balance once. A reported task is closed, so submit a new one to try again.

Yes. Put Bypass.java in your source tree, add a package line that matches its folder, and call Bypass.solve from anywhere. It has no dependencies to declare.

Policy

Responsible use

Use it where you are allowed to

Automate pages you own, test environments you run, or sites whose terms permit automated access. Do not use bypass.so for fraud, account takeover, spam, scalping, evading bans or collecting personal data without a lawful basis. Keep request rates reasonable and stop when a site asks you to. Our acceptable use policy applies to every key, and keys used for abuse are closed.

Keep going

Next steps