根本原因是OpenJDK默认不包含字体库和图形渲染后端;需安装系统字体包(如dejavu-sans-fonts)并执行sudo fc-cache -fv刷新缓存,再使用逻辑字体名(如"sansserif")替代硬编码字体。

OpenJDK在Linux无桌面环境(如CentOS/Ubuntu服务器)中调用AWT生成图片(如验证码、图表)时抛NullPointerException或NoClassDefFoundError: sun.awt.X11GraphicsEnvironment,根本原因不是代码写错了,而是OpenJDK默认不带字体和图形渲染后端——它压根没装“画图的笔和纸”。
为什么Font和Graphics2D在无GUI服务器上会空指针
Oracle JDK自带完整字体库(如DejaVu Sans)和headless图形栈;OpenJDK则剥离了这些非核心组件。当你的代码执行到new Font(...)或g2d.drawString(...)时,底层尝试加载系统字体配置,但/usr/lib/jvm/.../jre/lib/fonts/目录为空,FontManager初始化失败,后续所有Graphics2D操作都因font为null而崩。
- 典型错误堆栈开头是:
java.lang.NullPointerException at sun.awt.FontConfiguration.getVersion - 即使加了
-Djava.awt.headless=true也无效——这个参数只禁用GUI交互,不提供字体资源 -
openjdk-11+已正式归档AWT/Swing项目,后续版本可能彻底移除相关类
在CentOS/Ubuntu上补全字体依赖的实操步骤
不是装个.ttf文件就行,得让JVM能通过FontManager发现并加载它们。关键在于安装系统级字体包 + 刷新字体缓存。
- CentOS/RHEL 7/8:
sudo yum install -y fontconfig dejavu-sans-fonts liberation-fonts - Ubuntu/Debian:
sudo apt install -y fontconfig fonts-dejavu-core fonts-liberation - 安装完必须刷新缓存:
sudo fc-cache -fv(否则JVM仍读不到新字体) - 验证是否生效:
fc-list | grep -i sans应输出至少DejaVu Sans或Liberation Sans
AWT代码里绕过字体硬编码的写法
别再写new Font("微软雅黑", ..., ...)——服务器根本没有这个字体。改用逻辑字体名,让JVM fallback到已安装字体:
- 用
Font.decode("dialog")或Font.decode("sansserif")代替具体字体名 - 创建时显式指定大小:
new Font("sansserif", Font.BOLD, 14) - 避免调用
getAvailableFontFamilyNames()——它在无GUI下极慢且可能返回空数组 - 如果必须用中文,确保系统已装
fonts-wqy-microhei(Ubuntu)或wqy-microhei-fonts(CentOS),然后用"WenQuanYi Micro Hei"
更稳妥的替代方案:放弃AWT,改用Java2D兼容库
如果你的场景只是生成简单图片(如验证码、条形码、小图标),AWT已是技术债。推荐直接切换到轻量级替代:
-
BufferedImage+Graphics2D本身可用,但必须配合上面字体方案 - 用
Apache Commons Imaging(纯Java,无本地依赖)处理基础图像操作 - 生成文字优先考虑
SVG(文本格式,无字体绑定),再用batik-rasterizer转PNG - 高并发场景直接上
ImageMagick命令行:convert -size 200x60 xc:white -fill black -pointsize 24 -draw "text 10,40 'ABC'" captcha.png
真正容易被忽略的是:fc-cache -fv必须以root权限运行,且某些容器镜像(如openjdk:11-jre-slim)连fontconfig命令都不带——这时候得先apt install -y fontconfig再跑缓存命令,顺序错一步就白配。











