升级Helidon与Jersey后遭遇MappableException问题求助
Helidon/Jersey升级后API出现IO异常问题
问题背景
将Helidon升级至2.5.4、Jersey升级至2.35后,某一POST API出现IO异常,此前在Helidon 2.0.2与Jersey 2.29.1版本中该API运行正常。
错误日志
{"level":"ERROR","logger":"org.glassfish.jersey.server.ServerRuntime$Responder","thread":"jersey-thread-7","ts":1671639547627,"x-request-id":"","msg":"An I/O error has occurred while writing a response message entity to the container output stream.:\norg.glassfish.jersey.server.internal.process.MappableException: java.io.IOException: Bad news: the stream has been closed\n\tat org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:67)\n\tat org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)\n\tat org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1116)\n\tat org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:635)\n\tat org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:373)\n\tat org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:363)\n\tat org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:258)\n\tat org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)\n\tat org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)\n\tat org.glassfish.jersey.internal.Errors.process(Errors.java:292)\n\tat org.glassfish.jersey.internal.Errors.process(Errors.java:274)\n\tat org.glassfish.jersey.internal.Errors.process(Errors.java:244)\n\tat org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)\n\tat org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)\n\tat org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)\n\tat io.helidon.webserver.jersey.JerseySupport$JerseyHandler.lambda$doAccept$4(JerseySupport.java:335)\n\tat io.helidon.common.context.Contexts.runInContext(Contexts.java:117)\n\tat io.helidon.common.context.ContextAwareExecutorImpl.lambda$wrap$7(ContextAwareExecutorImpl.java:154)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat java.base/java.lang.Thread.run(Thread.java:834)\nCaused by: java.io.IOException: Bad news: the stream has been closed\n\tat io.helidon.webserver.jersey.ResponseWriter$DataChunkOutputStream.awaitRequest(ResponseWriter.java:327)\n\tat io.helidon.webserver.jersey.ResponseWriter$DataChunkOutputStream.write(ResponseWriter.java:186)\n\tat org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:200)\n\tat org.glassfish.jersey.message.internal.WriterInterceptorExecutor$UnCloseableOutputStream.write(WriterInterceptorExecutor.java:276)\n\tat com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2171)\n\tat com.fasterxml.jackson.core.json.UTF8JsonGenerator._writeBytes(UTF8JsonGenerator.java:1260)\n\tat com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeFieldName(UTF8JsonGenerator.java:284)\n\tat com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:726)\n\tat com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)\n\tat com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
伴随的警告日志
{"level":"WARN","logger":"io.helidon.webserver.BareResponseImpl","thread":"jersey-thread-1","ts":1671689369384,"x-request-id":"","msg":"Entity was requested and not fully consumed before a response is sent. This is not supported. Connection will be closed. Please fix your route for POST /demo/api/v2/test/","podname":"deployment-demo-batch-0"}
异常API签名
@POST @Path("test") @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Timed(name="app_kpi", absolute = true, reusable = true, unit=MetricUnits.NANOSECONDS,displayName="test", tags = {"api=test"}) @Tag(name = "Version2") @APIResponse(responseCode = "200", description = "Success", content = @Content(schema = @Schema(type=SchemaType.ARRAY, implementation = OutputRecord.class))) @APIResponse(responseCode = "403", description = "Forbidden") @APIResponse(responseCode = "401", description = "Unauthorized") @APIResponse(responseCode = "500", description = "An unexpected error occurred during the request.") @Authenticated public Vector<OutputRecords> test(@RequestBody(description = "An array of input records to be processed.", required = true, content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = InputRecord.class))) Vector<InputRecords> inputRecords)
问题分析与解决建议
核心原因
警告日志明确指出:请求体未被完全消费就发送了响应,这是Helidon WebServer在新版本中严格执行的规范,旧版本可能未做强制校验。当响应发送后,请求流被关闭,后续Jackson序列化响应时尝试写入已关闭的流,就会触发Bad news: the stream has been closed异常。
解决步骤
- 检查请求体消费逻辑:确认API方法中是否对
inputRecords做了部分读取操作(比如只遍历了部分元素),确保完全消费请求体后再返回响应。 - 替换集合类型:将方法参数和返回值的
Vector改为List(比如ArrayList)。Vector是老旧的线程安全集合,Jackson序列化在新版本中可能存在兼容问题,且Jersey默认的消息转换器对List的支持更稳定。 - 显式消费请求体:如果业务中不需要完整的请求体,可在方法开头显式读取并丢弃请求流,确保流被完全消费。
- 排查序列化问题:检查
OutputRecords类的Jackson序列化逻辑,确保没有自定义序列化器导致的流操作异常。 - 核对版本变更:查阅Helidon 2.x升级文档,确认WebServer与Jersey集成部分是否有请求体处理的破坏性变更,按需调整配置。
内容的提问来源于stack exchange,提问作者Eli Johnes
相关产品推荐
相关产品推荐

