Backend Development 10 min read

Automatic Unit Conversion in Java Using Reflection and Custom Annotations

This article demonstrates how to automatically convert monetary, percentage, permillage and other numeric fields in Java DTOs by marking target properties with a map or a custom annotation, then applying reflection‑based logic to perform scaling, rounding and unit changes in a reusable utility class.

IT Xianyu
IT Xianyu
IT Xianyu
Automatic Unit Conversion in Java Using Reflection and Custom Annotations

When processing statistical data, the raw values often do not match the business requirements: amounts may need to be expressed in ten‑thousands, percentages must be multiplied by 100, permillage values require a factor of 1000, and many fields need a fixed number of decimal places.

The first solution uses a Map<String, UnitConvertType> to mark which fields require conversion. An enum UnitConvertType defines the conversion types (R, B, PERCENTAGE, PERMIL). The utility class UnitConvertUtil iterates over a list of DTOs, reflects each field, checks the map, and applies the appropriate BigDecimal operation (divide, multiply, setScale) before writing the transformed value back.

public enum UnitConvertType {
    /** 精度 */
    R,
    /** 万元 */
    B,
    /** 百分 */
    PERCENTAGE,
    /** 千分 */
    PERMIL
}
public class UnitConvertUtil {
    public static
void unitMapConvert(List<T> list, Map<String, UnitConvertType> propertyMap) {
        for (T t : list) {
            Field[] declaredFields = t.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (propertyMap.keySet().stream().anyMatch(x -> x.equals(declaredField.getName()))) {
                    try {
                        declaredField.setAccessible(true);
                        Object o = declaredField.get(t);
                        UnitConvertType unitConvertType = propertyMap.get(declaredField.getName());
                        if (o != null) {
                            if (unitConvertType.equals(UnitConvertType.PERCENTAGE)) {
                                BigDecimal bd = ((BigDecimal) o).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                                declaredField.set(t, bd);
                            }
                            if (unitConvertType.equals(UnitConvertType.PERMIL)) {
                                BigDecimal bd = ((BigDecimal) o).multiply(new BigDecimal(1000)).setScale(2, BigDecimal.ROUND_HALF_UP);
                                declaredField.set(t, bd);
                            }
                            if (unitConvertType.equals(UnitConvertType.B)) {
                                BigDecimal bd = ((BigDecimal) o).divide(new BigDecimal(10000)).setScale(2, BigDecimal.ROUND_HALF_UP);
                                declaredField.set(t, bd);
                            }
                            if (unitConvertType.equals(UnitConvertType.R)) {
                                BigDecimal bd = ((BigDecimal) o).setScale(2, BigDecimal.ROUND_HALF_UP);
                                declaredField.set(t, bd);
                            }
                        }
                    } catch (Exception ex) {
                        log.error("处理失败");
                        continue;
                    }
                }
            }
        }
    }
}

Calling this method with a populated list and a map that links field names to UnitConvertType values converts all marked fields in one pass, as shown by the printed before‑and‑after lists.

To make the API more declarative, a custom annotation @JcBigDecConvert is introduced. The annotation carries a UnitConvertType value, allowing each DTO field to be directly annotated with the desired conversion.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JcBigDecConvert {
    UnitConvertType name();
}

The DTO MyYearSumReportDTO demonstrates the usage:

@Data
public class MyYearSumReportDTO implements Serializable {
    private static final long serialVersionUID = 5285987517581372888L;

    @JcBigDecConvert(name = UnitConvertType.B)
    private BigDecimal payTotalAmount;

    @JcBigDecConvert(name = UnitConvertType.PERCENTAGE)
    private BigDecimal jcAmountPercentage;

    @JcBigDecConvert(name = UnitConvertType.PERMIL)
    private BigDecimal jcCountPermillage;

    @JcBigDecConvert(name = UnitConvertType.R)
    private BigDecimal length;

    @JcBigDecConvert(name = UnitConvertType.R)
    private BigDecimal width;
}

A new utility method unitAnnotateConvert scans the fields, reads the annotation, and applies the same conversion logic as before, but without needing an external map.

public static
void unitAnnotateConvert(List<T> list) {
    for (T t : list) {
        Field[] declaredFields = t.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            try {
                if (declaredField.getName().equals("serialVersionUID")) continue;
                JcBigDecConvert ann = declaredField.getAnnotation(JcBigDecConvert.class);
                if (ann == null) continue;
                UnitConvertType type = ann.name();
                declaredField.setAccessible(true);
                Object o = declaredField.get(t);
                if (o != null) {
                    if (type.equals(UnitConvertType.PERCENTAGE)) {
                        BigDecimal bd = ((BigDecimal) o).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                        declaredField.set(t, bd);
                    }
                    if (type.equals(UnitConvertType.PERMIL)) {
                        BigDecimal bd = ((BigDecimal) o).multiply(new BigDecimal(1000)).setScale(2, BigDecimal.ROUND_HALF_UP);
                        declaredField.set(t, bd);
                    }
                    if (type.equals(UnitConvertType.B)) {
                        BigDecimal bd = ((BigDecimal) o).divide(new BigDecimal(10000)).setScale(2, BigDecimal.ROUND_HALF_UP);
                        declaredField.set(t, bd);
                    }
                    if (type.equals(UnitConvertType.R)) {
                        BigDecimal bd = ((BigDecimal) o).setScale(2, BigDecimal.ROUND_HALF_UP);
                        declaredField.set(t, bd);
                    }
                }
            } catch (Exception ex) {
                log.error("处理失败");
            }
        }
    }
}

Running the annotation‑based converter on a list of MyYearSumReportDTO objects produces the same correctly transformed values, while keeping the conversion rules close to the data model.

Overall, the article provides a reusable pattern for automatic numeric unit conversion in Java backend services, first with a map‑driven approach and then with a cleaner annotation‑driven solution.

backendJavaReflectionannotationsUtilityUnit Conversion
IT Xianyu
Written by

IT Xianyu

We share common IT technologies (Java, Web, SQL, etc.) and practical applications of emerging software development techniques. New articles are posted daily. Follow IT Xianyu to stay ahead in tech. The IT Xianyu series is being regularly updated.

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.