Java subtitle generator API: burn captions with HttpClient
Generate subtitles from Java with the JDK HttpClient: POST the video URL to a captions API, poll the job, then read the captioned video_url.

To generate subtitles from Java, you don't need a Java subtitle library: call a captions API over HTTPS with the JDK's java.net.http.HttpClient. With Sume, POST the video's public URL to https://api.sume.com/v1/video-captions, poll the job's status_url until terminal is true, then read GET /v1/video-captions/{id} for the captioned video_url. Sume transcribes the speech and burns the words into the picture.
Sume facts come from the Video captions and Jobs and results docs and the Sume API reference. Java behavior comes from the Java SE 21 pages for HttpClient and HttpRequest.Builder, and Jackson's databind README. All were read on 2026-09-29. Anything called current behavior is read from Sume's code. The same flow in Python is How to add subtitles to a video in Python.
What do I need before I start?
- Java 11 or newer:
HttpClienthas been in the JDK since 11. Sume's SDK is a TypeScript client, and everything it does is also reachable over plain HTTP, so Java calls the API directly. - A JSON library. The code uses Jackson's
jackson-databind, whose README says to create oneObjectMapperand reuse it. - Your API key in the
SUME_API_KEYenvironment variable of a server. Sume's docs say not to place keys in frontend JavaScript or mobile apps. - The video at a public HTTPS URL. Localhost, private-network, non-HTTPS, and signed or private URLs are rejected.
- In current code, a source of 60 seconds or less that has an audio stream.
How do I send the video from Java?
Build one HttpClient and reuse it: the JDK docs say a built client is immutable and can send multiple requests. Set a timeout on each request, because not setting one is the same as an infinite wait. The Idempotency-Key makes a retry of the same body return the original job instead of billing a second one.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import java.util.Map;
public class SumeCaptions {
static final String API = "https://api.sume.com/v1";
static final HttpClient HTTP = HttpClient.newHttpClient();
static final ObjectMapper JSON = new ObjectMapper(); // create once, reuse
static JsonNode call(HttpRequest.Builder req) throws Exception {
HttpResponse<String> res = HTTP.send(req.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("SUME_API_KEY")).build(),
HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 300) throw new IllegalStateException(res.statusCode() + " " + res.body());
return JSON.readTree(res.body()).get("data");
}
static JsonNode submit(String videoUrl, String key) throws Exception {
String body = JSON.writeValueAsString(Map.of("video_url", videoUrl, "language", "en"));
return call(HttpRequest.newBuilder(URI.create(API + "/video-captions"))
.header("Content-Type", "application/json").header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(body)));
}How do I wait for the captioned video?
Poll status_url, sleeping for next_poll_after_seconds between reads, until terminal is true. A client-side timeout does not cancel the job: it keeps running and still bills, so keep the id to resume from. Then read the caption resource and check its status.
| Step | Request | Fields the code reads |
|---|---|---|
| Submit | POST /v1/video-captions with video_url and an Idempotency-Key header | status_url, video_caption_id |
| Poll | GET the status_url | terminal, next_poll_after_seconds |
| Read | GET /v1/video-captions/{id} | status, video_url, error |
public static void main(String[] args) throws Exception {
JsonNode job = submit("https://example.com/clip.mp4", "clip-captions-001");
URI statusUrl = URI.create(job.get("status_url").asText());
JsonNode status = job;
do {
Thread.sleep(1000L * Math.max(1, status.path("next_poll_after_seconds").asInt(5)));
status = call(HttpRequest.newBuilder(statusUrl));
} while (!status.get("terminal").asBoolean());
JsonNode caption = call(HttpRequest.newBuilder(
URI.create(API + "/video-captions/" + job.get("video_caption_id").asText())));
if (!"completed".equals(caption.get("status").asText()))
throw new IllegalStateException(caption.get("error").toString());
System.out.println(caption.get("video_url").asText());
}
}Can I control the wording and the look?
languageis only a speech-to-text hint (en,ko, …); omit it for automatic detection.script_textkeeps the speech-to-text timings and aligns the burned wording to your script. Alignment can fail withscript_alignment_mismatchorscript_alignment_failed.cues(text,start,endin seconds) burn your own lines and skip speech-to-text. Sume takes no .srt upload, so turn SRT blocks into cues first.stylepicks the look, such asslam,punch, ortiktok-green; customize burned-in captions covers the options.
Can I get an SRT file instead of burned-in subtitles?
Not from the caption job: it returns a captioned video, and raw transcripts are not part of its public contract. For a sidecar file, transcribe with STT 1.0 (POST /v1/stt-1.0/transcribe) and segmentation.mode: "sentence", which groups the timed words into sentences, then write each segment as a numbered SRT block yourself. Speech to text API in Java has the Java call, and generate an SRT file from a video covers the format.
What are the limits, and what does it cost?
- Each accepted caption job reserves and captures $0.20 for videos up to 60 seconds, plus a 5.5% agent fee by default. Speech to text for an SRT is $0.01 per audio minute.
- In current code the caption job refuses a source over 60 seconds or one with no audio stream, even when you send cues. Split a longer video first.
- A clip with no audible speech fails as
caption_no_speech. - Script alignment is described for English or Korean voiceover; don't count on other languages for
script_text.
Sources
Related posts
More in Developers
- Suno alternative with an API: what Sume's Music Router does
Looking for a music generator you can call from code? What Sume's Music Router takes in, returns and does not do, so you can decide if it fits.
- Talking avatar in JS: make one from Node, play it in React
A talking avatar in JavaScript: create it and send it a script from Node with the Sume SDK, wait for the job, then play the returned MP4 in React.
- Avatar video quality settings: standard, plus or max?
Sume's talking avatar video takes quality standard, plus (default) or max. What each means, the other output fields, and how the preview relates to final tier.
- C# text to speech: call a TTS API with HttpClient
Text to speech in C#: POST the text and a voice with HttpClient, poll the job until it finishes, then stream the MP3 from its audio_url to a file.
Written by Sume