Sitemap

Spring AI Recipe: Adding Voice to AI

4 min readJun 17, 2026

--

Talking to an AI application feels surprisingly natural — until it answers with a wall of text. In this recipe, you’ll close that gap by adding text-to-speech support, enabling your Spring AI application to speak its responses aloud.

Press enter or click to view image in full size

In the previous recipe, you taught your Spring AI application how to listen. But a truly conversational experience requires more than understanding spoken input — it also needs the ability to speak back. In this recipe, you’ll take the next step and add speech synthesis so that your Spring AI application can speak its answers aloud.

Playing Audio Responses

We’ll use a library called JLayer to play MP3 audio through your computer’s speakers. Add the following dependency to your build:

implementation 'javazoom:jlayer:1.0.1'

Note: JLayer is licensed under the LGPL. If that license doesn’t fit your needs, you can use a different MP3 playback library. Alternatively, you can write the generated audio to a file and let another application play it.

Next, create a SpeechService that converts text into speech and plays the resulting audio:

@Service
public class SpeechService {

private final TextToSpeechModel speechModel;

public SpeechService(TextToSpeechModel speechModel) {
this.speechModel = speechModel;
}

public void speak(String text) {
byte[] audioBytes = speechModel.call(text);
try (var in = new ByteArrayInputStream(audioBytes)) {
new Player(in).play();
} catch (Exception e) {
System.err.println("Unable to speak: " + e.getMessage());
}
}

}

A few things are happening here:

  • SpeechService is injected with a TextToSpeechModel .
  • Because the application is already using OpenAI and Spring AI’s auto-configuration, that bean should already be available.
  • The speak() method accepts the text to be spoken.
  • The text is sent to OpenAI’s text-to-speech model, which returns MP3 audio as a byte array.
  • A ByteArrayInputStream is created from those bytes and passed to JLayer's Player.
  • Calling play() sends the audio to your computer's speakers.
  • If anything goes wrong, an error message is written to the console.

If you prefer not to use JLayer, you could simply write the MP3 bytes to a file:

Files.write(Path.of("response.mp3"), audioBytes);

You could then play the file using any MP3 player. Likewise, in a web application you might return those bytes directly to the browser and let the browser handle playback.

Speaking Chat Responses

Now wire SpeechService into the chat loop by injecting it into the ApplicationRunner:

@Bean
ApplicationRunner go(
ChatClient chatClient,
AudioRecorder recorder,
Transcriber transcriber,
SpeechService speechService) {
return args -> {
System.out.println("How can I help?\n");

try (Scanner scanner = new Scanner(System.in)) {
while (true) {

// ... remaining chat loop code ...

var answer = chatClient.prompt(input)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, "DEMO"))
.call()
.content();
System.out.println("\n - " + answer);
speechService.speak(answer);
}
}
};

}

For the sake of brevity, I’ve omitted the rest of the chat loop. The important addition is the call to speechService.speak(answer) immediately after receiving the response from the LLM.

One thing to be aware of is that Player.play() blocks until playback is complete. That's perfectly acceptable for a simple command-line application like this, but in a more sophisticated application you might want playback to happen on a separate thread.

Trying It Out

Run the application. At the prompt, either:

  • Type a question directly, or
  • Use /record and /stop from the previous recipe to speak your question.

To keep response times fast and token usage low, try something that results in a short answer: “Tell me a joke about penguins.” If everything is working correctly, you’ll see the answer printed to the console and then hear it spoken through your computer’s speakers.

Choosing a Different Voice

OpenAI offers several voices:

  • alloy
  • ash
  • ballad
  • cedar
  • coral
  • echo
  • fable
  • marin
  • nova
  • onyx
  • sage
  • shimmer
  • verse

By default, Spring AI uses OpenAI’s default voice (currently alloy). You can change the voice in code by using a TextToSpeechPrompt ( fable in this case):

public void speak(String text) {
TextToSpeechPrompt prompt =
new TextToSpeechPrompt(
text,
TextToSpeechOptions.builder()
.voice("fable")
.build());

TextToSpeechResponse response = speechModel.call(prompt);
byte[] audioBytes = response.getResult().getOutput();

try (var in = new ByteArrayInputStream(audioBytes)) {
new Player(in).play();
} catch (Exception e) {
System.err.println("Unable to speak: " + e.getMessage());
}
}

Alternatively, you can configure a default voice in your application properties:

spring.ai.openai.audio.speech.voice=fable

When configured this way, every call to the speech model will use that voice by default. If you later provide a voice through TextToSpeechPrompt, it will override the configured default.

Coming up in the next recipe, we’ll make a relatively small change that results in a much richer selection of voices. Stay tuned.

Get the code for this and other Spring AI recipes at https://github.com/habuma/spring-ai-recipes.

If you’re exploring Spring AI, I’ve written a book (Spring AI in Action) that covers the core foundations — and I’ll continue sharing newer patterns like this here as the ecosystem evolves. You can find my book on the publisher’s website, at Amazon, or anywhere you buy technical books.

--

--

Craig Walls
Craig Walls

Written by Craig Walls

Author Spring AI in Action, Spring in Action, and Build Talking Apps for Alexa