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.

4 min readSume
All posts

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

All Developers posts

Written by Sume