How to Generate Custom Captcha Images in Spring Boot with Hutool

Learn step‑by‑step how to create various graphical captchas—including line, circle, shear, GIF, and custom numeric or arithmetic types—in a Spring Boot application, using both a hand‑written utility class and the Hutool‑captcha library, with full code examples and controller integration.

Architect's Guide
Architect's Guide
Architect's Guide
How to Generate Custom Captcha Images in Spring Boot with Hutool

Introduction

This article explains how to generate graphical verification codes (captchas) for a login page in a Spring Boot project, covering both a custom utility class and the Hutool‑captcha library.

Hand‑written Captcha

Creating the utility class

public class Code {
    public static final String RANDOMCODEKEY = "ValidateCode";
    private Random random = new Random();
    private String randomString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private int width = 80;
    private int height = 26;
    private int lineSize = 40;
    private int stringNum = 4;
    private Font getFont() {
        return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
    }
    private Color getRandColor(int fc, int bc) {
        if (fc > 255) fc = 255;
        if (bc > 255) bc = 255;
        int r = fc + random.nextInt(bc - fc - 16);
        int g = fc + random.nextInt(bc - fc - 14);
        int b = fc + random.nextInt(bc - fc - 18);
        return new Color(r, g, b);
    }
    private String getRandomString(int num) {
        return String.valueOf(randomString.charAt(num));
    }
    private String drawString(Graphics g, String randomString, int i) {
        g.setFont(getFont());
        g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));
        String rand = String.valueOf(getRandomString(random.nextInt(this.randomString.length())));
        randomString += rand;
        g.translate(random.nextInt(3), random.nextInt(3));
        g.drawString(rand, 13 * i, 16);
        return randomString;
    }
    private void drawLine(Graphics g) {
        int x = random.nextInt(width);
        int y = random.nextInt(height);
        int xl = random.nextInt(13);
        int yl = random.nextInt(15);
        g.drawLine(x, y, x + xl, y + yl);
    }
    public void getValidateCode(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        Graphics g = image.getGraphics();
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
        g.setColor(getRandColor(110, 133));
        for (int i = 0; i <= lineSize; i++) {
            drawLine(g);
        }
        String randomString = "";
        for (int i = 1; i <= stringNum; i++) {
            randomString = drawString(g, randomString, i);
        }
        session.removeAttribute(RANDOMCODEKEY);
        session.setAttribute(RANDOMCODEKEY, randomString);
        System.out.println(randomString);
        g.dispose();
        try {
            ImageIO.write(image, "JPEG", response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Place this class in a util package; it generates a random image and stores the code in the HTTP session.

Calling from a controller

@GetMapping("/checkCode2")
public void checkcode2(HttpServletRequest request, HttpServletResponse response) {
    response.setContentType("image/jpeg");
    response.setDateHeader("Expires", 0);
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    Code code = new Code();
    code.getValidateCode(request, response);
}

Verification with Apifox

The endpoint created above can be tested with API testing tools such as Apifox to ensure the image is returned correctly.

Using Hutool Captcha

Adding the dependency

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.8.6</version>
</dependency>

Or import the full library with hutool-all if more utilities are needed.

LineCaptcha – line interference

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(130, 38, 5, 5);
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

CircleCaptcha – circular interference

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(130, 38, 5, 20);
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

ShearCaptcha – distorted interference

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(130, 38, 5, 5);
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

GifCaptcha – animated GIF

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    GifCaptcha captcha = CaptchaUtil.createGifCaptcha(130, 38, 5);
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

Custom Captcha

Numeric captcha

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
    captcha.setGenerator(randomGenerator);
    captcha.createCode();
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

Alphabetic captcha

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    RandomGenerator randomGenerator = new RandomGenerator("abcdefghijklmnopqrstuvwyzx", 4);
    CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100);
    captcha.setGenerator(randomGenerator);
    captcha.createCode();
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}

Arithmetic captcha

@GetMapping("/checkCode1")
public void checkCode(HttpServletResponse response) throws IOException {
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
    captcha.setGenerator(new MathGenerator());
    captcha.createCode();
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Javabackend developmentSpring BootCaptchaHutool
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.