Solve hCaptcha in Java.
NoneCap is a captcha solver service that focuses on hCaptcha. From
Java it is two HTTP calls: send the sitekey and page URL, get back a real
P1_ token. Below is a small client built on java.net.http,
then the token going into a page with Selenium WebDriver, with Playwright for Java,
and with no browser at all.
What a Java program needs
Two endpoints: POST /v1/solves to create a solve and
GET /v1/solves/{id} to read it, with your API key as a Bearer
token and JSON both ways. You send the sitekey and the page URL, and the response
carries the token as a string. Your program only places it in the form.
The examples need Java 17 or later and one library, Gson, for the JSON. Put your
key in NONECAP_KEY; the code reads it from the environment, so it never
sits in source control.
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.15.0</version>
</dependency> implementation("com.google.code.gson:gson:2.15.0") The client in four small files
Records for the request and the solve, with Gson mapping Java’s camelCase names onto the API’s snake_case fields:
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public final class NoneCap {
// userAgent <-> user_agent; null fields are left out of the request.
static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
// type: "hcaptcha", or "hcaptcha_enterprise" with the page's rqdata.
public record SolveRequest(String type, String sitekey, String url,
String rqdata, String proxy) {}
// status: pending | solving | solved | failed | expired | cancelled
public record Solve(String id, String status, String token, String userAgent,
Integer creditsCharged, Error error) {}
public record Error(String code, String message) {}
} An exception that carries the API’s error code:
public final class NoneCapException extends RuntimeException {
public final int status;
public final NoneCap.Error error; // null when the body is not the API's JSON
public NoneCapException(int status, NoneCap.Error error) {
super("NoneCap HTTP " + status
+ (error == null ? "" : ": " + error.code() + " " + error.message()));
this.status = status;
this.error = error;
}
private record Envelope(NoneCap.Error error) {}
static NoneCapException from(int status, String body) {
try {
var error = NoneCap.GSON.fromJson(body, Envelope.class).error();
return new NoneCapException(status, error);
} catch (RuntimeException notJson) {
return new NoneCapException(status, null);
}
}
} The HTTP call, with the key on every request:
import java.io.IOException;
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublishers;
final class NoneCapHttp {
private final HttpClient http = HttpClient.newHttpClient();
private final String apiKey;
NoneCapHttp(String apiKey) { this.apiKey = apiKey; }
NoneCap.Solve send(String path, NoneCap.SolveRequest body)
throws IOException, InterruptedException {
var req = HttpRequest.newBuilder(URI.create("https://api.nonecap.com/v1/" + path))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json");
if (body != null) req.POST(BodyPublishers.ofString(NoneCap.GSON.toJson(body)));
var res = http.send(req.build(), HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 300) throw NoneCapException.from(res.statusCode(), res.body());
return NoneCap.GSON.fromJson(res.body(), NoneCap.Solve.class);
}
} And the client your code calls, which returns when the solve is finished:
import java.io.IOException;
public final class NoneCapClient {
private final NoneCapHttp http;
public NoneCapClient(String apiKey) { this.http = new NoneCapHttp(apiKey); }
public NoneCap.Solve solve(NoneCap.SolveRequest req)
throws IOException, InterruptedException {
var solve = http.send("solves?wait=90", req);
while (solve.status().equals("pending") || solve.status().equals("solving"))
solve = http.send("solves/" + solve.id() + "?wait=90", null);
return solve;
}
public String solveHCaptcha(String sitekey, String url)
throws IOException, InterruptedException {
var solve = solve(new NoneCap.SolveRequest("hcaptcha", sitekey, url, null, null));
if (solve.token() == null) throw new IllegalStateException("solve " + solve.status());
return solve.token();
}
} Calling it:
public class Main {
public static void main(String[] args) throws Exception {
var nonecap = new NoneCapClient(System.getenv("NONECAP_KEY"));
String token = nonecap.solveHCaptcha(
"f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8", "https://target.example/login");
System.out.println(token); // P1_eyJ0eXAi...
}
} ?wait=90 asks the server to hold the request until the solve is
finished, so the token usually comes back on the first response and there is no
polling loop to write. If the solve is still running after 90 seconds, the API
answers 202 with status pending or solving, and the loop
asks again with GET ?wait=90. HttpClient has no request
timeout by default, so the held request is never cut short on your side.
A non-2xx response becomes a NoneCapException with the API’s error
code, such as unauthorized, insufficient_credits
or concurrency_limit_exceeded; the error
reference lists them all. A solve that ends failed or
expired is not charged, so a retry costs time, not credits.
If you would rather not hold connections open (some proxies and serverless hosts cut
long requests), drop ?wait: the POST returns at once with status
pending, and you read GET /v1/solves/{id} every
couple of seconds until the status changes.
Selenium WebDriver for Java
Both browser examples use the same script to put the token into the page. The
h-captcha-response textarea is in the top document, not inside the
widget’s iframe, so the script reaches it with no frame switch. It also fills
g-recaptcha-response, which some backends read instead, and calls the
page’s data-callback function: invisible widgets submit from that
callback, not from a field change.
public final class Inject {
// Fills the response textareas and calls the page's data-callback, if it has one.
public static final String TOKEN = """
token => {
for (const name of ['h-captcha-response', 'g-recaptcha-response']) {
const el = document.querySelector(`textarea[name="${name}"]`);
if (el) el.value = token;
}
const cb = document.querySelector('[data-callback]')?.getAttribute('data-callback');
if (cb && typeof window[cb] === 'function') window[cb](token);
}""";
} With selenium-java (4.6 or later fetches the matching chromedriver for you), the flow is: read the sitekey off the page, get a token, write it into the form, submit.
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;
public class SeleniumExample {
public static void main(String[] args) throws Exception {
var nonecap = new NoneCapClient(System.getenv("NONECAP_KEY"));
var driver = new org.openqa.selenium.chrome.ChromeDriver();
try {
driver.get("https://target.example/login");
var widget = driver.findElement(By.cssSelector("[data-sitekey]"));
var url = driver.getCurrentUrl();
var token = nonecap.solveHCaptcha(widget.getDomAttribute("data-sitekey"), url);
driver.executeScript("(" + Inject.TOKEN + ")(arguments[0])", token);
driver.findElement(By.cssSelector("[type=submit]")).click();
new WebDriverWait(driver, java.time.Duration.ofSeconds(15))
.until(ExpectedConditions.stalenessOf(widget));
} finally {
driver.quit();
}
}
} ChromeDriver runs scripts itself, so there is no cast to
JavascriptExecutor, and getDomAttribute reads the sitekey
exactly as the page wrote it. Waiting for the widget to go stale keeps the program
alive until the next page has loaded, even when the form posts back to the same URL,
and the finally block closes the browser even when a step throws.
If the site rejects the token, make sure the browser’s user agent matches the
solve’s returned user_agent: some sites refuse a token submitted
by a different browser version. If a page keeps its sitekey out of the DOM and passes
it straight to hcaptcha.render(), read the sitekey parameter
off the hcaptcha.com/1/api.js script URL instead; the
sitekey guide shows where to look.
Playwright for Java
The same steps with Playwright for Java
(com.microsoft.), which downloads its browsers the
first time Playwright.create() runs.
import com.microsoft.playwright.*;
public class PlaywrightExample {
public static void main(String[] args) throws Exception {
var nonecap = new NoneCapClient(System.getenv("NONECAP_KEY"));
try (Playwright pw = Playwright.create(); Browser browser = pw.chromium().launch()) {
Page page = browser.newPage();
page.navigate("https://target.example/login");
var sitekey = page.locator("[data-sitekey]").first().getAttribute("data-sitekey");
String token = nonecap.solveHCaptcha(sitekey, page.url());
page.evaluate(Inject.TOKEN, token);
page.locator("[type=submit]").click();
page.waitForLoadState();
}
}
} page.evaluate passes the token in as the function’s argument, so
nothing is pasted into the script text. click() waits for the navigation
it starts, and waitForLoadState() then waits for the next page to load. The
Playwright guide covers telling hCaptcha apart
from reCAPTCHA and Turnstile in the DOM, which applies unchanged in Java.
Without a browser: HttpClient and a form POST
Many sites don’t need a browser at all. Fetch the page, solve, and post the form
with the token in h-captcha-response. Keep one cookie jar and one exit IP
for the page load, the solve and the submit: give NoneCap the same proxy your
HttpClient uses, and send the solve’s returned
user_agent as your User-Agent header.
import java.net.*;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
public class FormPostExample {
public static void main(String[] args) throws Exception {
var nonecap = new NoneCapClient(System.getenv("NONECAP_KEY"));
var url = URI.create("https://target.example/login");
var site = HttpClient.newBuilder().cookieHandler(new CookieManager())
.proxy(ProxySelector.of(new InetSocketAddress("203.0.113.10", 8080))).build();
var html = site.send(HttpRequest.newBuilder(url).build(), BodyHandlers.ofString());
var m = java.util.regex.Pattern.compile("data-sitekey=\"(.+?)\"").matcher(html.body());
var solve = nonecap.solve(new NoneCap.SolveRequest("hcaptcha",
m.find() ? m.group(1) : "", url.toString(), null, "http://203.0.113.10:8080"));
if (solve.token() == null) throw new IllegalStateException("solve " + solve.status());
var form = HttpRequest.BodyPublishers.ofString("h-captcha-response=" + solve.token());
site.send(HttpRequest.newBuilder(url).POST(form).header("User-Agent", solve.userAgent())
.header("Content-Type", "application/x-www-form-urlencoded").build(),
BodyHandlers.ofString());
}
}
A proxy with a username and password goes to NoneCap as
http://user:pass@host:port. On the Java side it needs an
Authenticator on the HttpClient. For HTTPS sites, also start
the JVM with the system property jdk.http.auth.
set to an empty value: Java turns off Basic proxy auth for tunnels by default.
Tokens are single-use and expire within minutes, so solve right before you submit.
The proxy guide covers the proxy
formats and why a token solved on one exit can fail when submitted from another, and
the web scraping guide walks the whole fetch,
solve and submit pipeline.
Many solves at once
A fixed thread pool runs solves side by side with a bound on how many are in flight.
Size it to your account’s concurrency cap, not to your core count: the threads
spend their time waiting on the network, and requests past the cap come back as 429
concurrency_limit_exceeded. The API
reference lists the cap for each tier.
import java.util.List;
import java.util.concurrent.*;
public class FanOutExample {
public static void main(String[] args) throws Exception {
var nonecap = new NoneCapClient(System.getenv("NONECAP_KEY"));
var sitekey = "f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8";
var urls = List.of("https://target.example/login", "https://target.example/signup");
// Size the pool to your account's concurrency cap; past it the API answers 429.
ExecutorService pool = Executors.newFixedThreadPool(5);
try {
List<Callable<String>> jobs = urls.stream()
.map(u -> (Callable<String>) () -> nonecap.solveHCaptcha(sitekey, u))
.toList();
for (Future<String> token : pool.invokeAll(jobs)) System.out.println(token.get());
} finally {
pool.shutdown();
}
}
} Enterprise sitekeys
Some enterprise deployments pass a fresh rqdata value into
hcaptcha.render() or execute(). When the page supplies one,
send it with the enterprise type; the request record already has the field:
var solve = nonecap.solve(
new NoneCap.SolveRequest("hcaptcha_enterprise", sitekey, pageUrl, rqdata, null));
Capture rqdata right before solving; it is tied to one challenge. The
rqdata guide shows where it lives
in the page.
Scope, honestly
NoneCap solves hCaptcha only: regular, invisible, and enterprise sitekeys. If your Java program is facing reCAPTCHA or Cloudflare Turnstile, this API won’t help today. Check which widget the page loads before writing the integration.
Last updated November 2026.
Frequently asked
Is there an official Java SDK?
How do I solve hCaptcha in Selenium with Java?
driver.findElement(By.cssSelector("[data-sitekey]")).getDomAttribute("data-sitekey"), send it with driver.getCurrentUrl() to POST /v1/solves, then write the returned token into the h-captcha-response textarea with JavascriptExecutor.executeScript and call the data-callback function if the page sets one. The textarea is in the top document, so there is no switchTo().frame(). The Selenium example on this page does all four steps.Which Java version do the examples need?
java.net.http.HttpClient (Java 11), records (Java 16) and a text block (Java 15). The only library is Gson 2.10 or later, which reads JSON into records. With Jackson instead, set PropertyNamingStrategies.SNAKE_CASE and leave nulls out with JsonInclude.Include.NON_NULL.Can I use a 2Captcha Java client with NoneCap?
in.php and res.php calls at https://api.nonecap.com/compat/2captcha/ , so hand-written code that builds those URLs needs only the new base URL and your NoneCap key. The 2captcha-java package can change its host but not add a path, so it cannot reach that endpoint without replacing its ApiClient; the client on this page is shorter. The API reference has the full mapping.