Java 21: poll many Sume jobs with virtual threads and a Semaphore
Poll 50 Sume jobs from one Java 21 file using virtual threads, a Semaphore cap of 8, and java.net.http. No Maven, no dependencies, runs with java Poll.java.

Why virtual threads fit
Polling is waiting: a thread sends a GET, sleeps for the suggested interval, and sends another. Java 21 virtual threads make that cheap enough to give every job its own thread, so the code can stay a plain loop instead of a scheduler.
The limit you do care about is the read budget. Each GET to a Sume status route is one read against a per-key, per-minute allowance (4,800 a minute on the Free plan, as of the docs I read on 2026-10-05). A Semaphore caps how many requests are in flight at once.
One file
Save as Poll.java, pass job ids as arguments, and run java Poll.java job_a job_b. It prints each job's final line. The loop stops on terminal true.
import java.net.URI;
import java.net.http.*;
import java.util.concurrent.*;
import java.util.regex.*;
public class Poll {
public static void main(String[] ids) throws Exception {
var http = HttpClient.newHttpClient();
var key = System.getenv("SUME_API_KEY");
var cap = new Semaphore(8);
var done = Pattern.compile("\"terminal\"\\s*:\\s*true");
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (var id : ids) pool.submit(() -> {
var req = HttpRequest.newBuilder(URI.create(
"https://api.sume.com/v1/jobs/" + id + "/status"))
.header("Authorization", "Bearer " + key).build();
while (true) {
cap.acquire();
String body;
try { body = http.send(req, HttpResponse.BodyHandlers.ofString()).body(); }
finally { cap.release(); }
if (done.matcher(body).find()) { System.out.println(id + " done"); return null; }
Thread.sleep(5000);
}
});
}
}
}Before production
- Parse the JSON with a real library and read next_poll_after_seconds instead of the fixed 5000 ms sleep.
- Handle 429: stop and wait the retry-after seconds; the read bucket is separate from submits.
- Add an overall deadline per job; 20 minutes is the SDK default for jobs.
- Polling more jobs does not make them run faster: concurrency per plan is 1 on Free, 4 on Pro, 8 on Startup and 20 on Scale for generation.
Sources
Related posts
More in Developers
- Java ImageIO.read returns null on a Sume WebP: request PNG
ImageIO.read gives null when no reader claims the stream. Check the reader list, then pin output_format to png or jpeg on a Sume /v1/images call.
- Start the trim step from a job.completed webhook: Python verifier
Let Sume's signed job.completed webhook trigger the next chain step. A stdlib Python verifier that refuses an empty secret, plus dedupe on job_id.
- Job id or run id: which Sume endpoint and helper do you poll?
A job_ id is polled at /v1/jobs/:id with waitForJob. An arun_ id is a Format, Action or Agent run, polled with waitForRun and a family argument.
- job_id, request_id, Idempotency-Key: which one goes in which column
Your Idempotency-Key is the unique key before submit, job_id or run_id is the poll key, and request_id dedupes webhooks and goes into support tickets.
Written by Sume