调用getoutputstream()后再调用getwriter()会抛illegalstateexception,因servlet容器规定二者互斥且响应输出模式一旦锁定不可切换,典型场景是过滤器中写日志后转发至json控制器。

为什么调用 getOutputStream() 后再调用 getWriter() 会抛 IllegalStateException
因为 Servlet 容器规定:响应体输出流只能被获取一次,且 getOutputStream() 和 getWriter() 互斥。一旦其中任一方法被调用,响应的“输出模式”就锁定了——二进制流或字符流,不能再切换。
常见错误现象:java.lang.IllegalStateException: getWriter() has already been called for this response 或反过来。
- 典型场景:在过滤器或拦截器里写了日志(比如用
response.getWriter().write(...)),然后又转发给一个返回 JSON 的 Controller - 真实原因不是“多线程竞争”,而是同一个
HttpServletResponse实例上对输出通道的重复/冲突访问 - 注意:即使你没显式调用,某些框架封装(如 Spring 的
@ResponseBody)内部也会调用getWriter(),所以手动提前写响应极易撞车 - 修复思路:避免直接操作响应体;改用
HttpServletResponseWrapper拦截并缓存输出,或统一走ResponseEntity/@ControllerAdvice处理
Spring MVC 中 AsyncTaskExecutor 配置不当导致 IllegalStateException: Not allowed to start async request
这个异常本质是 Servlet 异步上下文未正确初始化,而不是线程池本身出错。
常见错误现象:用了 @Async 或手动调用 request.startAsync(),但容器没开启异步支持,或 DispatcherServlet 没配置 async-supported=true。
立即学习“Java免费学习笔记(深入)”;
- 检查 web.xml:确保
<servlet></servlet>标签下有<async-supported>true</async-supported>(Spring Boot 默认已配好,但嵌入式 Tomcat 版本低于 8.5 可能不生效) - Spring Boot 用户注意:
@EnableAsync必须加在配置类上,且自定义TaskExecutor时别漏掉setThreadNamePrefix("async-")—— 虽不直接引发异常,但会让问题更难排查 - 别在
Filter里调用startAsync()后又没做asyncContext.complete(),残留的异步上下文会污染后续请求
IllegalStateException 在 RecyclerView.Adapter 中的典型诱因:notifyXXX 调用时机错乱
Android 开发中,这个异常往往不是数据错了,而是通知刷新的线程或生命周期不对。
常见错误现象:IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling。
- 根本原因:在
RecyclerView正在布局(比如onLayout()执行中)或滚动动画进行时,主线程直接调了notifyDataSetChanged()或notifyItemInserted() - 不要用
Handler.post(Runnable)“绕过”这个问题——它只是延迟执行,不能保证避开布局期;正确做法是用adapter.notifyDataSetChanged()前先检查recyclerView.isComputingLayout(),为 true 就 defer 到下一帧(用View.post()) - 千万别在子线程更新数据后,直接在子线程调
notifyXXX—— 这会触发更隐蔽的崩溃,且不一定报这个异常 - 使用 DiffUtil 时,确保
calculateDiff()在后台线程,但dispatchUpdatesTo()一定在主线程,且不能嵌套在runOnUIThread的多次调用里
Java 8 Stream 复用导致的 IllegalStateException: stream has already been operated upon or closed
Stream 不是集合,是一次性消费的数据管道。复用它就像对同一个 InputStream 读两次 —— 不是逻辑错,是模型错。
常见错误现象:把 stream 赋给变量反复调用 collect()、forEach(),或者在 if 分支里各用一次。
- 最安全做法:每次需要 stream 行为时,都从原始集合重新生成,比如
list.stream().filter(...).count()和list.stream().map(...).toList()分开写 - 如果性能敏感且数据源开销大(比如 DB 查询结果),先
toList()或toSet()缓存中间结果,再基于集合创建新 stream - 别用
Supplier<stream></stream>包一层来“假装可重用”——这只会掩盖问题,且容易在并发下出错 - 注意
Stream.iterate()和Stream.generate()是无限流,没限制就直接collect()会 OOM,这不是IllegalStateException,但常和它一起出现
状态检查不是加一堆 if (obj != null) 就完事;关键是理解每个 API 对象的生命周期契约。比如 Response 的输出流、Stream 的单次性、RecyclerView 的布局阶段——它们不是“可能出错”,而是“只要违反契约就必然崩”。这点容易被忽略,但恰恰是 debug 时最该先看的。










