SpringBoot Sensitive Data Desensitization via Jackson Serialization (Zero‑Intrusion, High‑Performance)

This article explains how to protect user privacy in SpringBoot applications by masking sensitive fields such as phone numbers, ID cards, emails, bank cards, names, and addresses during JSON serialization using a custom Jackson serializer and annotation, offering a zero‑intrusion, high‑performance solution compared to other approaches.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
SpringBoot Sensitive Data Desensitization via Jackson Serialization (Zero‑Intrusion, High‑Performance)

1. Comparison of desensitization schemes

Manual utility class : Calls a utility method in business code; highly invasive and results in redundant code.

AOP aspect : Intercepts the response and masks data; parses the whole JSON, leading to poorer performance and difficulty handling complex nested objects.

MyBatis layer : Masks data during SQL result mapping; only works for database queries and cannot uniformly handle other API responses.

Jackson serialization layer (this solution) : Uses a custom serializer; zero business‑code intrusion, high performance, flexible, supports nested objects, and works globally.

2. Core principle

SpringBoot uses Jackson to convert Java objects to JSON strings before sending them to the front‑end. By defining a custom JsonSerializer and a meta‑annotation, fields annotated with @Desensitize are automatically masked during this serialization step.

3. Implementation workflow

Backend queries the database and obtains the full original entity object.

The controller returns the object; Jackson begins serialization.

The serializer detects the @Desensitize annotation on each field.

According to the annotation’s DesensitizeType, the corresponding masking method is invoked.

The front‑end receives JSON with masked values; the original data never leaves the server.

4. Maven dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-desensitization</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Web core includes Jackson -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

5. Application configuration (application.yml)

server:
  port: 8080

6. Unified result wrapper

import lombok.Data;

@Data
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMsg("操作成功");
        result.setData(data);
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMsg(msg);
        return result;
    }
}

7. Desensitization type enum

public enum DesensitizeType {
    DEFAULT,
    PHONE,      // 138****1234
    ID_CARD,   // 320123********1234
    EMAIL,     // zh****@example.com
    BANK_CARD, // 6222****1234
    NAME,      // 张**
    ADDRESS    // 广东省深圳市南山区****
}

8. Desensitization utility class

import org.springframework.util.StringUtils;

public class DesensitizeUtil {
    public static String mask(String str, int start, int end) {
        if (StringUtils.isEmpty(str)) return str;
        int len = str.length();
        if (len <= start + end) return str;
        String prefix = str.substring(0, start);
        String suffix = str.substring(len - end);
        return prefix + "****" + suffix;
    }

    public static String phone(String phone) { return mask(phone, 3, 4); }
    public static String idCard(String idCard) { return mask(idCard, 6, 4); }
    public static String email(String email) {
        if (StringUtils.isEmpty(email) || !email.contains("@")) return email;
        int index = email.indexOf("@");
        String prefix = email.substring(0, index);
        String suffix = email.substring(index);
        String pre = prefix.length() > 2 ? prefix.substring(0, 2) : prefix;
        return pre + "****" + suffix;
    }
    public static String bankCard(String card) { return mask(card, 4, 4); }
    public static String name(String name) {
        if (StringUtils.isEmpty(name) || name.length() <= 1) return name;
        return name.charAt(0) + "**";
    }
    public static String address(String address) {
        if (StringUtils.isEmpty(address) || address.length() <= 8) return address;
        return address.substring(0, 8) + "****";
    }
}

9. Custom annotation

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeSerializer.class)
public @interface Desensitize {
    DesensitizeType value();
}

10. Custom Jackson serializer

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import java.io.IOException;

public class DesensitizeSerializer extends StdScalarSerializer<String> {
    public DesensitizeSerializer() { super(String.class); }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        Desensitize annotation = gen.getAnnotation(Desensitize.class);
        if (annotation == null) { gen.writeString(value); return; }
        DesensitizeType type = annotation.value();
        String result = value;
        switch (type) {
            case PHONE:      result = DesensitizeUtil.phone(value); break;
            case ID_CARD:    result = DesensitizeUtil.idCard(value); break;
            case EMAIL:      result = DesensitizeUtil.email(value); break;
            case BANK_CARD:  result = DesensitizeUtil.bankCard(value); break;
            case NAME:       result = DesensitizeUtil.name(value); break;
            case ADDRESS:    result = DesensitizeUtil.address(value); break;
            default: break;
        }
        gen.writeString(result);
    }
}

11. Test entity and controller

import lombok.Data;
import java.io.Serializable;

@Data
public class User implements Serializable {
    private Long id;
    private String username;
    @Desensitize(DesensitizeType.NAME) private String realName;
    @Desensitize(DesensitizeType.PHONE) private String phone;
    @Desensitize(DesensitizeType.ID_CARD) private String idCard;
    @Desensitize(DesensitizeType.EMAIL) private String email;
    @Desensitize(DesensitizeType.BANK_CARD) private String bankCard;
    @Desensitize(DesensitizeType.ADDRESS) private String address;
}

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/info")
    public Result<User> getUserInfo() {
        User user = new User();
        user.setId(1L);
        user.setUsername("admin");
        user.setRealName("张三三");
        user.setPhone("13812345678");
        user.setIdCard("320123199801011234");
        user.setEmail("[email protected]");
        user.setBankCard("6222021234567891234");
        user.setAddress("广东省深圳市南山区科技园高新园区1栋");
        return Result.success(user);
    }
}

12. Test result

{
  "code":200,
  "msg":"操作成功",
  "data":{
    "id":1,
    "username":"admin",
    "realName":"张**",
    "phone":"138****5678",
    "idCard":"320123********1234",
    "email":"zh****@163.com",
    "bankCard":"6222****1234",
    "address":"广东省深圳市南山区****"
  }
}

13. Advantages summary

Zero business intrusion : Only add the annotation on entity fields; controllers, services, and DAOs remain unchanged.

High performance : Leverages Jackson's native serialization without reflection or secondary JSON parsing.

Unified control : One global configuration applies to all interfaces.

Easy extension : Adding a new type only requires an enum entry and a utility method.

Broad compatibility : Works with single objects, lists, nested objects, and the common Result wrapper.

Data security : Original database records stay untouched; only masked data is exposed, satisfying compliance requirements.

14. Production extensions and optimizations

Dynamic custom rules allow specifying custom start/end positions without adding new enum values.

Environment‑based switch enables desensitization only in production, simplifying debugging in dev/test.

Interface exclusion configuration lets privileged admin endpoints return raw data.

Log‑desensitization prevents sensitive information from appearing in logs.

Null‑value handling in the serializer avoids NPEs.

15. Frequently asked questions

Annotation not effective : May be caused by static fields, missing getters, or an outdated Lombok version. Remove static, ensure getters are generated.

Null values are not masked : The utility methods already check for empty strings and return null unchanged.

Collections or List responses not masked : Jackson naturally iterates collections during serialization, so masking works without extra code.

Conflict with global Jackson config : Using @JacksonAnnotationsInside integrates the custom serializer without conflict.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaSpringBootAnnotationJacksonData Desensitization
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

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.