jeecgboot/JeecgBoot · error · IOException

生成验证码图片失败

Error message

生成验证码图片失败

What it means

Thrown by RandImageUtil.generate(HttpServletResponse, String) when ImageIO.write fails to write the captcha BufferedImage to the servlet response output stream. The method wraps any Exception (I/O error, image encoding failure, closed/disposed response stream) in an IOException with this message. Note that createVerifyCodeImage itself has an internal fallback (createErrorImage), so this outer exception typically originates from ImageIO.write or response.getOutputStream() rather than from the drawing logic.

Source

Thrown at jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/util/RandImageUtil.java:74

    private static final int CHAR_X_OFFSET = 8;

    /**
     * 直接通过response输出验证码图片
     * 
     * @param response HTTP响应对象
     * @param verifyCode 验证码字符串
     * @throws IOException 输出异常
     */
    public static void generate(HttpServletResponse response, String verifyCode) throws IOException {
        if (response == null || verifyCode == null || verifyCode.trim().isEmpty()) {
            throw new IllegalArgumentException("参数不能为空");
        }
        
        try {
            BufferedImage image = createVerifyCodeImage(verifyCode);
            ImageIO.write(image, IMG_FORMAT, response.getOutputStream());
        } catch (Exception e) {
            throw new IOException("生成验证码图片失败", e);
        }
    }

    /**
     * 生成验证码的base64字符串
     * 
     * @param verifyCode 验证码字符串
     * @return base64编码的图片字符串
     * @throws IOException 生成异常
     */
    public static String generate(String verifyCode) throws IOException {
        if (verifyCode == null || verifyCode.trim().isEmpty()) {
            throw new IllegalArgumentException("验证码不能为空");
        }
        
        try {
            BufferedImage image = createVerifyCodeImage(verifyCode);
            

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Ensure the Docker/base image has font packages: for Debian-based images install 'fonts-dejavu-core fontconfig'; for Alpine install 'fontconfig ttf-dejavu'.
  2. Verify the controller sets response.setContentType("image/jpeg") and does not call response.getWriter() anywhere before generate().
  3. Check that no filter or interceptor commits/flushes the response before the captcha endpoint executes.

Example fix

// before: no content-type header, possible writer conflict
@RestController
public class CaptchaController {
    @GetMapping("/captcha")
    public void captcha(HttpServletResponse response, String verifyCode) throws IOException {
        RandImageUtil.generate(response, verifyCode);
    }
}

// after: set content-type and reset buffer before writing
@GetMapping("/captcha")
public void captcha(HttpServletResponse response, String verifyCode) throws IOException {
    response.setContentType("image/jpeg");
    response.setHeader("Cache-Control", "no-store, no-cache");
    response.resetBuffer();
    RandImageUtil.generate(response, verifyCode);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure response is ready for binary output before calling generate
public void safeGenerateCaptcha(HttpServletResponse response, String verifyCode) {
    if (response == null || verifyCode == null || verifyCode.trim().isEmpty()) {
        throw new IllegalArgumentException("参数不能为空");
    }
    response.setContentType("image/jpeg");
    response.setHeader("Cache-Control", "no-store, no-cache");
    response.setHeader("Pragma", "no-cache");
    try {
        RandImageUtil.generate(response, verifyCode);
    } catch (IOException e) {
        log.error("Failed to generate captcha image", e);
    }
}

Try / catch

try {
    response.setContentType("image/jpeg");
    response.setHeader("Cache-Control", "no-store");
    RandImageUtil.generate(response, verifyCode);
} catch (IOException e) {
    log.error("Captcha image generation failed", e);
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}

Prevention

When it happens

Trigger: The HttpServletResponse output stream is already closed or committed before generate() is called. A headless server without proper AWT font configuration causes JPEG encoding to fail (though the static block sets java.awt.headless=true). The response content type was not set to image/jpeg before writing. Memory pressure causes BufferedImage allocation to fail. The servlet container has already flushed the response buffer.

Common situations: Running in a minimal Docker container without font packages installed (e.g., vanilla alpine without fontconfig/dejavu fonts). Calling response.getWriter() before response.getOutputStream() (illegal state). Interceptor/filter that commits the response before the captcha controller runs. Heavy load causing transient OOM during image allocation.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/02b07bce90cd075b. Report an issue: GitHub.