{"record":{"id":"58e6341f361d2548","repo":"spring-projects/spring-ai","slug":"failed-to-read-audio-speech-response","errorCode":null,"errorMessage":"Failed to read audio speech response","messagePattern":"Failed to read audio speech response","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java","lineNumber":143,"sourceCode":"\t\tAssert.notNull(prompt, \"Prompt must not be null\");\n\n\t\tOpenAiAudioSpeechOptions mergedOptions = mergeOptions(prompt);\n\t\tString inputText = getInputText(prompt, mergedOptions);\n\t\ttraceRequest(\"Calling\", mergedOptions);\n\n\t\tSpeechCreateParams params = buildSpeechCreateParams(mergedOptions, inputText, false);\n\n\t\tRequestOptions requestOptions = this.buildRequestOptions(mergedOptions);\n\n\t\tHttpResponse httpResponse = this.openAiClient.audio().speech().create(params, requestOptions);\n\t\tHeaders headers = httpResponse.headers();\n\n\t\tbyte[] audioBytes;\n\t\ttry (InputStream inputStream = httpResponse.body()) {\n\t\t\taudioBytes = inputStream.readAllBytes();\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to read audio speech response\", e);\n\t\t}\n\n\t\tif (audioBytes.length == 0) {\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"No speech response returned for prompt: \" + prompt);\n\t\t\t}\n\t\t\treturn new TextToSpeechResponse(List.of(new Speech(new byte[0])));\n\t\t}\n\n\t\tSpeech speech = new Speech(audioBytes);\n\t\tOpenAiAudioSpeechResponseMetadata metadata = OpenAiAudioSpeechResponseMetadata.from(headers);\n\n\t\treturn new TextToSpeechResponse(List.of(speech), metadata);\n\t}\n\n\t@Override\n\tpublic Flux<TextToSpeechResponse> stream(TextToSpeechPrompt prompt) {\n\t\tAssert.notNull(prompt, \"Prompt must not be null\");","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java#L125-L161","documentation":"OpenAiAudioSpeechModel.call() reads the entire HTTP response body of the OpenAI text-to-speech endpoint into memory via InputStream.readAllBytes(). If the stream throws an IOException mid-read (connection dropped, read timeout, connection reset), the method wraps it in a RuntimeException with this message, indicating the audio bytes could not be fully retrieved.","triggerScenarios":"The HTTP connection to the OpenAI speech endpoint is interrupted while streaming the audio bytes: network drop, proxy closing the connection, read timeout on a long/slow TTS generation, or the server aborting the response mid-transfer.","commonSituations":"Generating long audio files over unstable networks or corporate proxies with idle timeouts; Docker/Kubernetes environments where connections are dropped after ~30-60s; OpenAI API incidents; very large speech requests that exceed proxy body limits and cause the upstream to cut the stream.","solutions":["Retry the speech request, ideally with exponential backoff — transient network drops are the most common cause.","Enable response streaming / increase read timeouts on the underlying RestClient/ WebClient used by the OpenAI client.","Check proxy/firewall idle-timeout settings and disable response buffering for large audio payloads.","Verify OpenAI service status and reduce request size (shorter prompt, different voice/format) to shorten transfer time.","Inspect the wrapped IOException cause to distinguish timeout vs connection-reset vs premature EOF."],"exampleFix":"// before\nbyte[] audio = openAiAudioSpeechModel.call(new SpeechPrompt(longText));\n// after\nbyte[] audio;\ntry {\n    audio = openAiAudioSpeechModel.call(new SpeechPrompt(longText));\n}\ncatch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Failed to read audio speech response\")) {\n        audio = retryWithBackoff(() -> openAiAudioSpeechModel.call(new SpeechPrompt(longText)));\n    } else { throw e; }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    SpeechResponse response = speechModel.call(prompt);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Failed to read audio speech response\") && attempt < 3) {\n        // backoff and retry; transient IO failure reading the audio stream\n    } else {\n        throw e;\n    }\n}","preventionTips":["Configure generous read/response timeouts on the RestClient used by the OpenAI client.","For long prompts, split the text to keep each response transfer short.","Check proxy idle timeouts and disable response buffering for audio endpoints.","Implement idempotent retry with exponential backoff around TTS calls.","Monitor the IOException cause to distinguish timeout vs reset vs premature EOF."],"tags":["network","io","openai","audio","tts"],"backgroundTag":"network-request-failed","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}