Backend Development 17 min read

Implementing Encryption, Decryption, and Data Masking in Java Using Annotations, AOP, and Reflection

This article demonstrates how to protect sensitive user information in a Java backend by encrypting data before storage, decrypting and desensitizing it on retrieval, and applying these rules automatically through custom annotations, reflection, and Spring AOP aspects.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Encryption, Decryption, and Data Masking in Java Using Annotations, AOP, and Reflection

In response to government requirements that commercial software must not expose raw user data such as phone numbers, ID numbers, or addresses, the article first analyses the need for encrypted storage and masked display of personal information.

Solution Overview : Two implementation styles are compared – a "gob‑style" approach that manually encrypts/decrypts every field, and an "aspect‑oriented" approach that marks fields with custom annotations and lets AOP handle the logic centrally.

Annotation definitions are provided for fields and methods:

package com.weige.javaskillpoint.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
    DesensitizationEnum value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Decryption {}

The DesensitizationEnum enum lists the fields that require masking (name, address, phone).

package com.weige.javaskillpoint.enums;

public enum DesensitizationEnum {
    name,
    address,
    phone;
}

Two entity classes illustrate the usage:

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.DecryptField;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

public class UserDO {
    @DecryptField(DesensitizationEnum.name)
    private String name;
    @DecryptField(DesensitizationEnum.address)
    private String address;
    // getters, setters, constructors omitted for brevity
}

package com.weige.javaskillpoint.entity;

import com.weige.javaskillpoint.annotation.EncryptField;

public class UserBO {
    @EncryptField
    private String name;
    @EncryptField
    private String address;
    // getters, setters, constructors omitted for brevity
}

Reflection is used to process the annotations at runtime. The following snippet shows how UserDO fields are inspected, decrypted, and desensitized based on the enum value.

Object obj = new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae");
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
    if (field.isAnnotationPresent(DecryptField.class)) {
        field.setAccessible(true);
        String encrypted = (String) field.get(obj);
        DesensitizationEnum type = field.getAnnotation(DecryptField.class).value();
        String decrypted = (String) AesUtil.decrypt(encrypted, type);
        field.set(obj, decrypted);
    }
}

Spring AOP aspects intercept methods annotated with @Encryption or @Decryption . The encryption aspect encrypts fields marked with @EncryptField before the method proceeds, while the decryption aspect reverses the process after the method returns.

package com.weige.javaskillpoint.aop;

import com.weige.javaskillpoint.annotation.EncryptField;
import com.weige.javaskillpoint.utils.AesUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class EncryptAspect {
    @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
    public void point() {}

    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof UserBO) {
                for (Field f : arg.getClass().getDeclaredFields()) {
                    if (f.isAnnotationPresent(EncryptField.class)) {
                        f.setAccessible(true);
                        Object val = f.get(arg);
                        if (val != null) {
                            f.set(arg, AesUtil.encrypt(val));
                        }
                    }
                }
            }
        }
        return joinPoint.proceed();
    }
}

package com.weige.javaskillpoint.aop;

import com.weige.javaskillpoint.annotation.DecryptField;
import com.weige.javaskillpoint.enums.DesensitizationEnum;
import com.weige.javaskillpoint.utils.AesUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DecryptAspect {
    @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Decryption)")
    public void point() {}

    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        if (result != null) {
            for (Field f : result.getClass().getDeclaredFields()) {
                if (f.isAnnotationPresent(DecryptField.class)) {
                    f.setAccessible(true);
                    String encrypted = (String) f.get(result);
                    DesensitizationEnum type = f.getAnnotation(DecryptField.class).value();
                    String decrypted = (String) AesUtil.decrypt(encrypted, type);
                    f.set(result, decrypted);
                }
            }
        }
        return result;
    }
}

The utility class AesUtil encapsulates AES encryption/decryption using Hutool, and DesensitizationUtil applies simple masking (e.g., keeping the first character of a name, the first three of an address).

package com.weige.javaskillpoint.utils;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

public class AesUtil {
    public static final String AES_KEY = "Wk#qerdfdshbd910";
    public static final AES aes = SecureUtil.aes(AES_KEY.getBytes());

    public static String encrypt(Object obj) {
        return aes.encryptHex((String) obj);
    }

    public static String decrypt(Object obj) {
        return aes.decryptStr((String) obj, java.nio.charset.StandardCharsets.UTF_8);
    }
}

package com.weige.javaskillpoint.utils;

import cn.hutool.core.util.StrUtil;
import com.weige.javaskillpoint.enums.DesensitizationEnum;

public class DesensitizationUtil {
    public static String desensitization(Object obj, DesensitizationEnum type) {
        switch (type) {
            case name:    return StrUtil.hide((String) obj, 1, ((String) obj).length());
            case address: return StrUtil.hide((String) obj, 3, ((String) obj).length());
            default:      return "";
        }
    }
}

Finally, controller examples show how the annotations are applied to REST endpoints, allowing the framework to automatically encrypt incoming UserBO objects and decrypt outgoing UserDO objects.

package com.weige.javaskillpoint.controller;

import com.weige.javaskillpoint.annotation.Encryption;
import com.weige.javaskillpoint.entity.UserBO;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/encrypt")
public class EncryptController {
    @PostMapping("/v1")
    @Encryption
    public UserBO insert(@RequestBody UserBO user) {
        // user fields are encrypted by the aspect before this method returns
        return user;
    }
}

package com.weige.javaskillpoint.controller;

import com.weige.javaskillpoint.annotation.Decryption;
import com.weige.javaskillpoint.entity.UserDO;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/decrypt")
public class DecryptController {
    @GetMapping("/v1")
    @Decryption
    public UserDO decrypt() {
        // returned object will be decrypted and masked by the aspect
        return new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae");
    }
}

By copying the provided code into a Spring Boot project (dependencies listed in the article), developers can quickly set up a secure data‑handling pipeline that centralises encryption, decryption, and masking logic, reducing boilerplate and maintenance risk.

javaaopReflectionannotationsSpringBootencryptionDataMasking
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.