JDK 27 HttpClient: poll a Sume job in one Java file

JDK 27 reached GA on 15 September 2026. A single-file java.net.http loop for GET /v1/jobs/:id/status with timeouts and next_poll_after_seconds.

5 min readSume
All posts

Short answer

With JDK 27 you can run a Sume poll loop as one source file: java Poll.java job_123. Build one HttpClient, set a connect timeout and a per-request timeout, read terminal and next_poll_after_seconds off the status body, and sleep that long. The JDK 27 project page records General Availability on 15 September 2026.

There is no Sume-published Java SDK, so the loop below uses only the JDK. The contract it follows is on Sume's jobs and results page: async returns a job id, GET /v1/jobs/:id/status says when to look again, and terminal says when to stop.

What JDK 27 lists, and what it does not change here

None of the JDK 27 items below alter java.net.http. They are context for running the loop on JDK 27, and because Structured Concurrency is listed as a seventh preview, this post avoids it.

JDK 27 items from the project page (read 2026-10-03)
JEPTitle
523Make G1 the Default Garbage Collector in All Environments
534Compact Object Headers by Default
527Post-Quantum Hybrid Key Exchange for TLS 1.3
533Structured Concurrency

The loop

Two timeouts are set on purpose. A HttpClient has no overall request deadline unless you give one, so a stalled connection would otherwise hold the poller forever. The regex reads two fields from the JSON so the file needs no dependency; swap in Jackson if you already have it.

import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import java.util.regex.*;

public class Poll {
  public static void main(String[] a) throws Exception {
    var http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
    var req = HttpRequest.newBuilder(URI.create("https://api.sume.com/v1/jobs/" + a[0] + "/status"))
        .timeout(Duration.ofSeconds(20))
        .header("Authorization", "Bearer " + System.getenv("SUME_API_KEY")).build();
    var done = Pattern.compile("\"terminal\"\\s*:\\s*true");
    var next = Pattern.compile("\"next_poll_after_seconds\"\\s*:\\s*([0-9.]+)");
    while (true) {
      var res = http.send(req, HttpResponse.BodyHandlers.ofString());
      if (res.statusCode() != 200) throw new IllegalStateException(res.statusCode() + " " + res.body());
      if (done.matcher(res.body()).find()) { System.out.println(res.body()); return; }
      var m = next.matcher(res.body());
      Thread.sleep(m.find() ? (long) (Double.parseDouble(m.group(1)) * 1000) : 2000);
    }
  }
}

Reading the result

When sume_status is completed, call GET /v1/jobs/:id/result. Completed jobs carry result.artifacts, each with an id, type, url and content_type; the URLs are Sume media URLs, and raw provider URLs are never public outputs.

If the status is failed or canceled, do not call the result route. It answers 409 job_not_completed for anything that is not completed, so read the failure from GET /v1/jobs/:id instead.

What Sume does and does not do

Sume keeps the job alive when your JVM exits and lets you pick the id back up from any process that holds it. It does not extend sync mode past 30 seconds, and subscribe mode is an alias of sync, so a Java service that waits on the submit call will see a 2xx envelope that is not terminal and must fall back to this loop.

Polling spends the read budget, 40 times the write budget on every plan, and the ratelimit-remaining header tells you what is left. Back off on retry-after if a 429 ever appears.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume