You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug description
When using Spring-AI to communicate with OpenAI's API, the client fails to properly decode responses that use Brotli compression (Content-Encoding: br), resulting in a JSON parsing error. The HTTP response is received successfully (200 OK), but the parser encounters an "Unexpected end-of-input" error.
Environment
Spring AI 1.0.0-M6 and 1.0.0-SNAPSHOT
Java 21
Using OpenAI API integration with RestClient
Kotlin application
Steps to reproduce
Configure the OpenAI chat client in a Spring Boot application
Execute a simple prompt request as shown in the test:
classAITests {
@Autowired
privatelateinitvar chatClientBuilder:Builder
@Test
funtest() {
val client = chatClientBuilder.build()
val completion = client.prompt("Tell me joke about Spring Framework").call()
println(completion.content().orEmpty())
}
}
Expected behavior
The client should successfully decode the response from OpenAI, including responses with Brotli compression, and return the completed prompt content.
Minimal Complete Reproducible example
The above test reproduces the issue. It fails with the following exception:
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1])
Further in the logs, i can see the response headers contain:
Content-Encoding: br
The root issue appears to be that the Spring RestClient cannot properly decompress Brotli-encoded content before attempting to parse the JSON, resulting in a malformed input to the JSON parser.
This issue occurs with the standard Spring Boot RestClient setup. The HTTP exchange completes successfully, but the JSON cannot be properly decoded after receiving the Brotli-compressed response.
full log
D 20:13:09.634 [rk.retry.support.RetryTemplate] Retry: count=0
D 20:13:09.663 [k.web.client.DefaultRestClient] Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[rawContent=Tell me joke about Spring Framework, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null, audioOutput=null]], model=gpt-4-turbo, store=null, metadata=null, frequencyPenalty=null, logitBias=null, logprobs=null, topLogprobs=null, maxTokens=null, maxCompletionTokens=null, n=null, outputModalities=null, audioParameters=null, presencePenalty=null, responseFormat=null, seed=null, serviceTier=null, stop=null, stream=false, streamOptions=null, temperature=0.7, topP=null, tools=null, toolChoice=null, parallelToolCalls=null, user=null, reasoningEffort=null]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
D 20:13:09.667 [mpl.classic.InternalHttpClient] ex-0000000001 preparing request execution
D 20:13:09.676 [http.impl.classic.ProtocolExec] ex-0000000001 proxy auth state: UNCHALLENGED
D 20:13:09.676 [.http.impl.classic.ConnectExec] ex-0000000001 acquiring connection with route {s}->[https://api.openai.com:443]
D 20:13:09.676 [mpl.classic.InternalHttpClient] ex-0000000001 acquiring endpoint (3 MINUTES)
D 20:13:09.678 [ingHttpClientConnectionManager] ex-0000000001 endpoint lease request (3 MINUTES) [route: {s}->[https://api.openai.com:443]][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
D 20:13:09.681 [ingHttpClientConnectionManager] ex-0000000001 endpoint leased [route: {s}->[https://api.openai.com:443]][total available: 0; route allocated: 1 of 5; total allocated: 1 of 25]
D 20:13:09.692 [ingHttpClientConnectionManager] ex-0000000001 acquired ep-0000000001
D 20:13:09.692 [mpl.classic.InternalHttpClient] ex-0000000001 acquired endpoint ep-0000000001
D 20:13:09.692 [.http.impl.classic.ConnectExec] ex-0000000001 opening connection {s}->[https://api.openai.com:443]
D 20:13:09.693 [mpl.classic.InternalHttpClient] ep-0000000001 connecting endpoint (null)
D 20:13:09.693 [ingHttpClientConnectionManager] ep-0000000001 connecting endpoint to https://api.openai.com:443 (3 MINUTES)
D 20:13:09.694 [ltHttpClientConnectionOperator] api.openai.com resolving remote address
D 20:13:09.734 [ltHttpClientConnectionOperator] api.openai.com resolved to [api.openai.com/162.159.140.245, api.openai.com/172.66.0.243]
D 20:13:09.734 [ltHttpClientConnectionOperator] https://api.openai.com:443 connecting null->api.openai.com/162.159.140.245:443 (3 MINUTES)
D 20:13:09.752 [ltHttpClientConnectionOperator] http-outgoing-0 https://api.openai.com:443 connected /10.111.1.158:59176->api.openai.com/162.159.140.245:443
D 20:13:09.753 [ultManagedHttpClientConnection] http-outgoing-0 set socket timeout to 3 MINUTES
D 20:13:09.753 [ltHttpClientConnectionOperator] http-outgoing-0 https://api.openai.com:443 upgrading to TLS
D 20:13:09.762 [.ssl.AbstractClientTlsStrategy] Enabled protocols: [TLSv1.3, TLSv1.2]
D 20:13:09.762 [.ssl.AbstractClientTlsStrategy] Enabled cipher suites: [TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
D 20:13:09.762 [.ssl.AbstractClientTlsStrategy] Starting handshake (null)
#REDACTED#
D 20:13:10.172 [ jdk.event.security] TLSHandshake: api.openai.com:443, TLSv1.3, TLS_AES_256_GCM_SHA384, 307315732
D 20:13:10.172 [.ssl.AbstractClientTlsStrategy] Secure session established
D 20:13:10.172 [.ssl.AbstractClientTlsStrategy] negotiated protocol: TLSv1.3
D 20:13:10.172 [.ssl.AbstractClientTlsStrategy] negotiated cipher suite: TLS_AES_256_GCM_SHA384
D 20:13:10.173 [.ssl.AbstractClientTlsStrategy] peer principal: CN=api.openai.com
D 20:13:10.173 [.ssl.AbstractClientTlsStrategy] peer alternative names: [api.openai.com, *.api.openai.com]
D 20:13:10.173 [.ssl.AbstractClientTlsStrategy] issuer principal: CN=WE1, O=Google Trust Services, C=US
D 20:13:10.175 [ltHttpClientConnectionOperator] http-outgoing-0 https://api.openai.com:443 upgraded to TLS
D 20:13:10.175 [ingHttpClientConnectionManager] ep-0000000001 connected http-outgoing-0
D 20:13:10.175 [mpl.classic.InternalHttpClient] ep-0000000001 endpoint connected
D 20:13:10.176 [tp.impl.classic.MainClientExec] ex-0000000001 executing POST /v1/chat/completions
D 20:13:10.176 [ttp.protocol.RequestAddCookies] ex-0000000001 Cookie spec selected: strict
D 20:13:10.186 [mpl.classic.InternalHttpClient] ep-0000000001 start execution ex-0000000001
D 20:13:10.187 [ingHttpClientConnectionManager] ep-0000000001 executing exchange ex-0000000001 over http-outgoing-0
D 20:13:10.188 [apache.hc.client5.http.headers] http-outgoing-0 >> POST /v1/chat/completions HTTP/1.1
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Authorization: Bearer #REDUCTED#
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Content-Type: application/json
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Accept-Encoding: gzip, x-gzip, deflate, br
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Host: api.openai.com
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Transfer-Encoding: chunked
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> Connection: keep-alive
D 20:13:10.189 [apache.hc.client5.http.headers] http-outgoing-0 >> User-Agent: Apache-HttpClient/5.4.2 (Java/21)
D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >>"POST /v1/chat/completions HTTP/1.1[\r][\n]"
D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >>"Authorization: Bearer #REDUCTED#D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "Content-Type: application/json[\r][\n]"D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "Accept-Encoding: gzip, x-gzip, deflate, br[\r][\n]"D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "Host: api.openai.com[\r][\n]"D 20:13:10.207 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "Transfer-Encoding: chunked[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "Connection: keep-alive[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "User-Agent: Apache-HttpClient/5.4.2 (Java/21)[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "85[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "{"messages":[{"content":"Tell me joke about Spring Framework","role":"user"}],"model":"gpt-4-turbo","stream":false,"temperature":0.7}[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "0[\r][\n]"D 20:13:10.208 [rg.apache.hc.client5.http.wire] http-outgoing-0 >> "[\r][\n]"D 20:13:14.796 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "HTTP/1.1 200 OK[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "Date: Thu, 27 Feb 2025 19:13:14 GMT[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "Content-Type: application/json[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "Connection: keep-alive[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "access-control-expose-headers: X-Request-ID[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "openai-organization: #REDUCTED#[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"openai-processing-ms: 3617[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"openai-version: 2020-10-01[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-limit-requests: 500[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-limit-tokens: 30000[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-remaining-requests: 499[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-remaining-tokens: 29974[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-reset-requests: 120ms[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-ratelimit-reset-tokens: 52ms[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"x-request-id: req_b3f9020721078c7a9c296fc76fda05ca[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"strict-transport-security: max-age=31536000; includeSubDomains; preload[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"cf-cache-status: DYNAMIC[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"Set-Cookie: #REDUCTED#D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "X-Content-Type-Options: nosniff[\r][\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "Set-Cookie: #REDUCTED#
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"Server: cloudflare[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"CF-RAY: 918a7d96dd89c314-VIE[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"Content-Encoding: br[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"alt-svc: h3=":443"; ma=86400[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"1ac[\r][\n]"
D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"!p[\r][0x0] &[0xffffffe9]t[0xfffffff7]2]5[0xffffffc7][\r][0xfffffff0][0x17]a%2[0xffffffca]@[0xffffff8f][0xffffffa1][0xffffff82][0xffffffcf]s[0xffffffb9][0xffffffe7][0xfffffff7][0xe][0xffffff94][0xffffffd6][0xffffffc4][0xffffffb1]@[0xe]p[0xfffffffe][0x1b][0xb]lL[0xffffffbc][0xffffffab][0xffffff88][0xffffffba]0[\r]4[0xffffffe0]0,[0xffffff9d][0xffffffba][0xffffffe3]41[0xffffffb6]V`[0xffffffc6][0xf][0x18][0xffffffbe][0xfffffff7][0xffffffd7][0x3][0xffffffa8]LI[0xffffff80][0xffffff92]"rI[0xffffffab][0x1a][0xffffffff]|[0xffffff9a][0xffffffc6][0xffffffe5][0xfffffff5][0xfffffff8][0xffffffae][0xffffffdd][0xffffffbd]L[0xffffffb3][0xfffffff5]M{c[0xffffffaa][0x1b][0xffffff9d][0xffffffdf][0xffffffdb][0xffffffdc]e[0xffffffc3][0xffffff86]z[0x1e]@2[0xffffffae]8qw[0xffffffaa][0xfffffffb])[0x6]N);[\n]"D 20:13:14.797 [rg.apache.hc.client5.http.wire] http-outgoing-0 <<"[[0xffffffb9][0xfffffffc][0xffffff8c]S[0x12][0x18][0xffffffce]'[0xffffff83][0xffffffd9]b<][0xe][0xffffffc0]W[[0xffffff99]rC[0x2][0xffffff94]+[0xffffffe7]O|[0xffffffb7]2[0xffffffb1][0xfffffff4]G[0xffffff83][0xffffffd1][0xffffffc4][0x1f]L[0xfffffffc][0xffffffc1]R[0xffffffe1]"D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << HTTP/1.1 200 OKD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Date: Thu, 27 Feb 2025 19:13:14 GMTD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Content-Type: application/jsonD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Transfer-Encoding: chunkedD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Connection: keep-aliveD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << access-control-expose-headers: X-Request-IDD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << openai-organization: #REDUCTED#D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << openai-processing-ms: 3617D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << openai-version: 2020-10-01D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-limit-requests: 500D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-limit-tokens: 30000D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-remaining-requests: 499D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-remaining-tokens: 29974D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-reset-requests: 120msD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-ratelimit-reset-tokens: 52msD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << x-request-id: req_b3f9020721078c7a9c296fc76fda05caD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << strict-transport-security: max-age=31536000; includeSubDomains; preloadD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << cf-cache-status: DYNAMICD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Set-Cookie: #REDUCTED#D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << X-Content-Type-Options: nosniffD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Set-Cookie: #REDUCTED#D 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Server: cloudflareD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << CF-RAY: 918a7d96dd89c314-VIED 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << Content-Encoding: brD 20:13:14.802 [apache.hc.client5.http.headers] http-outgoing-0 << alt-svc: h3=":443"; ma=86400D 20:13:14.813 [rotocol.ResponseProcessCookies] ex-0000000001 Cookie accepted [#REDUCTED#]D 20:13:14.813 [rotocol.ResponseProcessCookies] ex-0000000001 Cookie accepted [#REDUCTED#]D 20:13:14.815 [tp.impl.classic.MainClientExec] ex-0000000001 connection can be kept alive for 3 MINUTESD 20:13:14.822 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "[0xffffffb4][0xffffff90]e[0xffffffc2][0xffffff96][0x4]><[0x0][0xfffffff8]=[0xffffffeb][0x0]4[0x3][0xffffffbf]M[0x2]z[0xffffffd4]U6#C'A[0xfffffffc][0x11]@F6L[0x2][0x14]Y[Z[0x17]u[0xffffff8e]z[0x4]ha[0xfffffff7][0xffffffa1][0x0][0xffffffbd][0x14];[0xffffffa4]e:[0xffffffe8]#$i|[0xffffff89]\"6[0xffffffb2][0xffffffe6][0xffffff93][0xffffffcf][0xffffffee][0xffffffb3][0xffffffab][0xffffffb1][0xffffffef][0x15][0xffffff8c][0xffffffca][0xfffffffd][0xffffff9b][0xffffffa8]i[0xffffffb0][0xffffffad][0xffffffd7][0xffffffbf]$[0x5]CvxP[0xffffffa6][0xffffffec]r[0xffffffc4][0x1c]uv[0xffffffef]Y0[0xffffffe6]~4jH[0xffffffa0][5[0xffffff8d][\r][0xffffffda][0xffffffbf][0xfffffff1][0xffffff91]F[0xffffffe6][0xffffffca][0xffffffc8][0xffffffd8]r8[0xffffff8c]m[0xe][0xffffffd7][0x8]6[0xffffffe2][0x1a]$[0x1][0xffffffb2]N*[0xffffff92][0xfffffff0][0xffffffff]=[0xffffffe0]+H[0xffffffed][0xffffff95][0xffffffdf][0xffffffcf][0xffffff90]2[0xffffffb2]U.p[0xffffffb2][0xffffffe6][0xffffffce][0xffffff92][0xffffffc0]p,[0xffffffd9][0xffffffa0][0xffffffa5]A[0x2][0xffffffb9][0xffffff80]?)[0x0][0xffffffd8][0xffffffd8]x[0xffffffd8]ST[0xffffff83]>vM[0x1b]d[0xffffff89][0xfffffffc]o[0xffffffa4][0xffffffaa][0xffffffb0]"[0xfffffffb]h[0xffffff95][0xffffff96][0x12]lz~[0xffffffe7][0x18][0xffffff94][0xffffffab]O[0xfffffffb]>[0xffffffbb][0xffffffec][0xfffffff2][0x3][0xffffffc0]D[0xffffff92][0xffffffb0]r[0xffffff9c][0x6][0xffffff9d][0x10][0xffffffd3]N[0xffffff83][0xffffffc6]m[0xffffff86]+N[0xffffffa8][0xffffffaa][0x17]g[0xffffffe2][0xffffff9f]k[0xffffffdf][0x17][0xf]\[0xffffffc9][0xffffff86][0x4][0xffffffe8][0xffffff83]C[0x13][0x1e][0xffffffed][0xffffffac][0xffffffe3]6[0xffffffc8][0xffffffca].g[0xffffffa3]L[0xfffffff9]N[0xffffffdd][0xffffffcf]T0Of[0xffffffe3][0xffffffc1]b[0xffffff9e]FC[0xfffffff2][0xfffffffe]=[0x3][0x3][\r][\n]"D 20:13:14.823 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "0[\r][\n]"D 20:13:14.823 [rg.apache.hc.client5.http.wire] http-outgoing-0 << "[\r][\n]"D 20:13:14.823 [mpl.classic.InternalHttpClient] ep-0000000001 releasing valid endpointD 20:13:14.823 [ingHttpClientConnectionManager] ep-0000000001 releasing endpointD 20:13:14.823 [ingHttpClientConnectionManager] ep-0000000001 connection http-outgoing-0 can be kept alive for 3 MINUTESD 20:13:14.823 [ingHttpClientConnectionManager] ep-0000000001 connection released [route: {s}->[https://api.openai.com:443]][total available: 1; route allocated: 1 of 5; total allocated: 1 of 25]D 20:13:14.860 [k.web.client.DefaultRestClient] Reading to [org.springframework.ai.openai.api.OpenAiApi$ChatCompletion]W 20:13:14.874 [SpringAiRetryAutoConfiguration] Retry error. Retry count:1org.springframework.web.client.RestClientException: Error while extracting response for type [org.springframework.ai.openai.api.OpenAiApi$ChatCompletion] and content type [application/json] at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:261) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.readBody(DefaultRestClient.java:814) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.lambda$toEntityInternal$2(DefaultRestClient.java:770) at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:574) at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:535) at org.springframework.web.client.RestClient$RequestHeadersSpec.exchange(RestClient.java:677) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.executeAndExtract(DefaultRestClient.java:809) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.toEntityInternal(DefaultRestClient.java:769) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.toEntity(DefaultRestClient.java:758) at org.springframework.ai.openai.api.OpenAiApi.chatCompletionEntity(OpenAiApi.java:257) at org.springframework.ai.openai.OpenAiChatModel.lambda$internalCall$1(OpenAiChatModel.java:274) at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:357) at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:230) at org.springframework.ai.openai.OpenAiChatModel.lambda$internalCall$3(OpenAiChatModel.java:274) at io.micrometer.observation.Observation.observe(Observation.java:564) at org.springframework.ai.openai.OpenAiChatModel.internalCall(OpenAiChatModel.java:271) at org.springframework.ai.openai.OpenAiChatModel.call(OpenAiChatModel.java:255) at org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1.aroundCall(DefaultChatClient.java:680) at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) at io.micrometer.observation.Observation.observe(Observation.java:564) at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:493) at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatResponse$1(DefaultChatClient.java:482) at io.micrometer.observation.Observation.observe(Observation.java:564) at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatResponse(DefaultChatClient.java:482) at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:466) at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.content(DefaultChatClient.java:516) at dev.fts.projectManuel2.email.EmailServiceIntegrationTests.test(EmailServiceIntegrationTests.kt:81) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:124) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:99) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:94) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92) at jdk.proxy2/jdk.proxy2.$Proxy6.stop(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:200) at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71) at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:409) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:357) at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:228) ... 108 common frames omittedCaused by: com.fasterxml.jackson.core.io.JsonEOFException: Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]) at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2] at com.fasterxml.jackson.core.base.ParserMinimalBase._reportInvalidEOF(ParserMinimalBase.java:641) at com.fasterxml.jackson.core.base.ParserBase._handleEOF(ParserBase.java:530) at com.fasterxml.jackson.core.base.ParserBase._eofAsNextChar(ParserBase.java:547) at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._skipWSOrEnd(UTF8StreamJsonParser.java:3066) at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:716) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:181) at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342) at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2125) at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1501) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:398) ... 110 common frames omittedD 20:13:14.875 [rk.retry.support.RetryTemplate] Checking for rethrow: count=1D 20:13:14.875 [rk.retry.support.RetryTemplate] Retry failed last attempt: count=1
The text was updated successfully, but these errors were encountered:
Bug description
When using Spring-AI to communicate with OpenAI's API, the client fails to properly decode responses that use Brotli compression (
Content-Encoding: br
), resulting in a JSON parsing error. The HTTP response is received successfully (200 OK), but the parser encounters an "Unexpected end-of-input" error.Environment
Steps to reproduce
Expected behavior
The client should successfully decode the response from OpenAI, including responses with Brotli compression, and return the completed prompt content.
Minimal Complete Reproducible example
The above test reproduces the issue. It fails with the following exception:
Further in the logs, i can see the response headers contain:
The root issue appears to be that the Spring RestClient cannot properly decompress Brotli-encoded content before attempting to parse the JSON, resulting in a malformed input to the JSON parser.
This issue occurs with the standard Spring Boot RestClient setup. The HTTP exchange completes successfully, but the JSON cannot be properly decoded after receiving the Brotli-compressed response.
full log
The text was updated successfully, but these errors were encountered: