web.xml 中 exception-type 必须写异常的全限定类名,写错则完全不生效;容器不匹配父类、不支持通配符,且受类加载、JDK模块、框架拦截等多重影响。

web.xml 里 exception-type 写错类名就完全不生效
Java Web 应用中,web.xml 的 error-page 配置依赖精确匹配异常的全限定类名(FQCN),写错一个字母、少个包名、用简称(比如写 NullPointerException 而不是 java.lang.NullPointerException),容器(Tomcat/Jetty)就当没这回事,直接走默认 500 页面或抛出堆栈。
常见错误现象:
- 配置了
exception-type却始终跳不到自定义错误页 - 本地 Tomcat 有效,上线后失效(可能因 JDK 版本差异导致异常实际抛出类型不同)
- 写了
RuntimeException,但子类异常(如IllegalArgumentException)没被拦截——因为容器只匹配声明的类,不向上查找父类
实操建议:
- 务必使用异常的完整类名,例如:
java.io.FileNotFoundException,不能简写为FileNotFoundException - 不确定具体抛出类型?在开发环境加个全局 filter 或 try-catch 打印
e.getClass().getName() - 若想捕获多个子类,需分别配置多个
error-page,无法用通配符或正则
Tomcat 9+ 对 exception-type 的匹配更严格,尤其涉及模块化类加载
JDK 9+ 引入模块系统后,部分异常类(如 java.nio.file.AccessDeniedException)可能来自不同模块,而旧版 web.xml 解析器不会自动 resolve 模块路径。Tomcat 9.0.31+ 默认启用更严格的类加载校验,若容器找不到该类(比如它被封装在 java.base 以外的模块且未显式导出),会静默忽略整个 error-page 块。
实操建议:
- 优先查 Tomcat 日志(
catalina.out),搜索 “error-page” 或 “exception-type”,看是否有 WARN 提示类未找到 - 避免依赖 JDK 内部异常(如
sun.*包下的类),它们在高版本已被移除或不可见 - 如需覆盖底层 I/O 异常,建议在业务层主动 catch 并 throw 自定义异常(如
com.example.FileAccessException),再在web.xml中配置该类
exception-type 和 error-code 同时存在时,优先级规则容易误判
一个请求同时触发 HTTP 状态码(如 404)和 Java 异常(如 ServletException),容器按“先匹配到谁就用谁”的策略处理,但这个顺序不是按 XML 书写顺序,而是由容器内部解析逻辑决定:通常 exception-type 优先于 error-code,但前提是异常确实被容器捕获并识别——如果异常在 Filter 链中被吞掉、或在异步 Servlet 中未传播到容器层面,那 exception-type 根本没机会触发。
实操建议:
- 不要指望靠
exception-type拦截所有 500 场景;它只对容器自身抛出的、未被上层代码 catch 的异常有效 - 调试时可临时把
error-code设为500,确认页面是否能正常跳转,排除 HTML/路径等基础问题 - 若用了 Spring MVC,
@ExceptionHandler会先于web.xml生效,此时exception-type形同虚设
部署时 classpath 变更可能导致 exception-type 类找不到
很多项目把异常类定义在某个 jar 包里(比如 common-exceptions.jar),开发时该 jar 在 WEB-INF/lib 下,web.xml 配置了 com.example.NotFoundException。但上线后运维把 jar 移到了共享 classpath(Tomcat lib/ 目录),或者用了 classloader 隔离机制(如 OSGi、JRebel),会导致容器在应用 classloader 中找不到这个类,从而跳过匹配。
实操建议:
- 检查异常类是否真的在当前 WebApp 的 classloader 可见范围内:写个 servlet 打印
this.getClass().getClassLoader().loadClass("com.example.NotFoundException") - 尽量将自定义异常类放在
WEB-INF/classes下,或确保其 jar 固定在WEB-INF/lib - 避免跨应用复用异常类而不同步部署,这是线上
exception-type失效最隐蔽的原因之一
真正难的不是写对那个类名,而是确认它在运行时确实存在、能被容器加载、且没被框架提前拦截——这三个条件缺一不可。










