HomeBlog → Speech to text in Java: code on the standard HttpClient

Speech to text in Java: code on the standard HttpClient

Published 2026-08-20 · 5 min read

Java has shipped an HTTP client since version 11, and that is all you need — no third-party libraries for uploading audio. The one annoyance is assembling the multipart body by hand. Here is code you can paste.

Building the multipart request

import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.io.ByteArrayOutputStream;

String boundary = "----vs" + System.nanoTime();
Path audio = Path.of("call.ogg");

var body = new ByteArrayOutputStream();
body.write(("--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n"
    + "--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"file\"; filename=\"" + audio.getFileName() + "\"\r\n"
    + "Content-Type: audio/ogg\r\n\r\n").getBytes());
body.write(Files.readAllBytes(audio));
body.write(("\r\n--" + boundary + "--\r\n").getBytes());

var request = HttpRequest.newBuilder(URI.create("https://voicesscribe.com/v1/audio/transcriptions"))
    .header("Authorization", "Bearer " + System.getenv("VS_KEY"))
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .timeout(java.time.Duration.ofMinutes(3))
    .POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray()))
    .build();

var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
    throw new IllegalStateException("error " + response.statusCode() + ": " + response.body());
}
System.out.println(response.body());

The line breaks in the boundaries must be carriage return plus line feed: with a bare newline the server cannot parse the body.

Parsing the response

The response is JSON with a text field. Any familiar parser will do — Jackson, Gson or your framework's built-in support:

record Transcription(String text) {}

var mapper = new com.fasterxml.jackson.databind.ObjectMapper();
var result = mapper.readValue(response.body(), Transcription.class);
System.out.println(result.text());

If you requested srt or vtt, the response is not JSON at all but a ready subtitle file — nothing to parse.

Under load

Three things worth doing from the start:

  1. Keep one HttpClient per application — it is thread-safe and reuses connections.
  2. Do not read large files fully into memory: BodyPublishers.ofFile streams them.
  3. Bound concurrency with a fixed thread pool sized to your plan's rate limit.

Common failures

SymptomCause
400 on a valid filemissing filename in the form part or a broken boundary
401the key never made it out of the environment variable
413file above 25 MB — compress or split
Cut off on long filesno timeout set on the request

Try it on your own recordings. Sign-up takes a minute, and the free minutes are enough to judge the quality.

Get a free API key

Frequently asked questions

Do I need OkHttp or Apache HttpClient?

Not necessarily: the built-in client is enough. OkHttp is nicer in that it assembles multipart bodies for you.

Does this work on Java 8?

No, the built-in HttpClient arrived in Java 11. On 8 use OkHttp or Apache HttpClient.

How do I pass the audio language?

Add another form part named language with a code such as en.

Can I stream the file instead of buffering?

Yes, BodyPublishers.ofFile sends it without loading it into memory, which matters for files near the size limit.

Related reading