Text to speech in Java, offline
Java never got speech synthesis in the standard library, so projects reach for older engines such as FreeTTS or MaryTTS, which sound robotic next to neural voices, or for a cloud SDK that bills per character and sees every text.
This guide shows a modern alternative: the free SuperClonica engine as a local HTTP server, called with the HttpClient built into Java 11 and newer. You get natural voices, WAV files you keep and no fee per request, on any platform.
Ways to speak from Java
The engine does not run inside the JVM; your Java code talks to it over HTTP, like to any other service.
- FreeTTS and MaryTTS: pure Java and offline, but robotic compared with neural voices.
- Cloud SDKs: natural voices, but an account, a key, a bill per character and your text on their servers.
- SuperClonica: a local HTTP server with natural, character and custom voices, offline, wherever Docker runs.
- It speaks 31 languages; Chinese is not supported yet.
Hear the voices free online
Before you write code, open the page of any voice, type a line of 30 to 100 characters, choose the language and press Voice it. In a few seconds you hear the voice your app could have, with no account.
Start the speech server
An NVIDIA graphics card makes it faster with the cuda image, but it is not required.
- Start the server on any CPU: docker run -d -p 8000:8000 -v superclonica-models:/models superclonica/superclonica:cpu.
- The first start downloads the Supertonic 3 model, about 385 MB, into the volume; GET /health tells you when it is ready.
- POST /synthesize takes JSON with text, voice, lang, seed and normalize, and answers with a 16-bit mono WAV.
- voice is a stock name from M1 to M5 and F1 to F5, or the JSON content of a voice file.
Call it from Java
With java.net.http, java.net.URI and java.nio.file, a request takes a few lines in Java 11 or newer: var voice = Files.readString(Path.of("voice.json")); var json = "{\"text\": \"Hello from Java!\", \"lang\": \"en\", \"seed\": 7, \"voice\": " + voice + "}"; var request = HttpRequest.newBuilder(URI.create("http://localhost:8000/synthesize")).header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(json)).build(); HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofFile(Path.of("hello.wav"))); The response is written straight to a WAV file. For a stock voice, send "voice": "F1" instead of the file content. The same text, voice and seed always give the same audio, so cache files for phrases that repeat; a JSON library such as Jackson keeps the request tidy in larger projects.
Production habits
Reuse one HttpClient and set a timeout on requests. Do not expose port 8000 to the internet as it is: set SUPERCLONICA_API_KEY and send it as a Bearer token, or keep the server on a private network. Run more copies of the model with SUPERCLONICA_LANES if many requests arrive at once, and measure on your hardware: in our tests a short phrase took 0.12 s on an RTX 5070 Ti and 0.43 s on four CPU threads.