How to Use Java Annotations and Reflection to Build Fixed‑Length Bank API Requests

This article demonstrates how to serialize bank API parameters into a fixed‑length string using Java annotations and reflection, covering the initial naïve implementation, its drawbacks, and a refactored solution that centralizes formatting, signing, and request logic for maintainable backend code.

Programmer DD
Programmer DD
Programmer DD
How to Use Java Annotations and Reflection to Build Fixed‑Length Bank API Requests

Case Scenario

Assume a bank provides APIs that require parameters to be serialized into a single fixed‑length string instead of JSON, with specific padding rules for different data types.

String parameters are left‑aligned and padded on the right with underscores.

Numeric parameters are right‑aligned and padded on the left with zeros.

Currency values are rounded down to two decimal places (cents) and treated as numeric with left padding.

The final string is signed using an MD5 hash.

Initial Code Implementation

public class BankService {
    // Create user method
    public static String createUser(String name, String identity, String mobile, int age) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        // String left‑aligned, pad with _
        stringBuilder.append(String.format("%-10s", name).replace(' ', '_'));
        stringBuilder.append(String.format("%-18s", identity).replace(' ', '_'));
        // Number right‑aligned, pad with 0
        stringBuilder.append(String.format("%05d", age));
        // String left‑aligned, pad with _
        stringBuilder.append(String.format("%-11s", mobile).replace(' ', '_'));
        // Append MD5 signature
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/createUser")
                .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)
                .execute().returnContent().asString();
    }

    // Payment method
    public static String pay(long userId, BigDecimal amount) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        // Number right‑aligned, pad with 0
        stringBuilder.append(String.format("%020d", userId));
        // Amount rounded down to cents, left‑padded with 0
        stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN)
                .multiply(new BigDecimal("100")).longValue()));
        // Append MD5 signature
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/pay")
                .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)
                .execute().returnContent().asString();
    }
}

This implementation meets the basic requirements but suffers from several problems:

Duplicated logic makes the code fragile and error‑prone.

String concatenation, signing, and request sending are repeated in every method.

Method signatures may not match the exact order required by the API, leading to mistakes.

Parameter values are hard‑coded, making verification difficult.

Optimizing with Interfaces and Reflection

Define POJO for All API Parameters

@Data
public class CreateUserAPI {
    private String name;
    private String identity;
    private String mobile;
    private int age;
}

Define Annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface BankAPI {
    String desc() default "";
    String url() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface BankAPIField {
    int order() default -1;
    int length() default -1;
    String type() default ""; // S: string, N: number, M: money
}

Reflection to Assemble Parameters Dynamically

private static String remoteCall(AbstractAPI api) throws IOException {
    // Get request URL from annotation
    BankAPI bankAPI = api.getClass().getAnnotation(BankAPI.class);
    StringBuilder stringBuilder = new StringBuilder();
    Arrays.stream(api.getClass().getDeclaredFields())
        .filter(f -> f.isAnnotationPresent(BankAPIField.class))
        .sorted(Comparator.comparingInt(f -> f.getAnnotation(BankAPIField.class).order()))
        .peek(f -> f.setAccessible(true))
        .forEach(f -> {
            BankAPIField fieldAnno = f.getAnnotation(BankAPIField.class);
            Object value = "";
            try { value = f.get(api); } catch (IllegalAccessException e) { e.printStackTrace(); }
            switch (fieldAnno.type()) {
                case "S":
                    stringBuilder.append(String.format("%-" + fieldAnno.length() + "s", value.toString())
                        .replace(' ', '_'));
                    break;
                case "N":
                    stringBuilder.append(String.format("%" + fieldAnno.length() + "s", value.toString())
                        .replace(' ', '0'));
                    break;
                case "M":
                    if (!(value instanceof BigDecimal))
                        throw new RuntimeException(String.format("%s's %s must be BigDecimal", api, f));
                    stringBuilder.append(String.format("%0" + fieldAnno.length() + "d",
                        ((BigDecimal) value).setScale(2, RoundingMode.DOWN)
                        .multiply(new BigDecimal("100")).longValue()));
                    break;
                default:
                    break;
            }
        });
    // Append signature
    stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
    String param = stringBuilder.toString();
    long begin = System.currentTimeMillis();
    String result = Request.Post("http://localhost:45678/reflection" + bankAPI.url())
            .bodyString(param, ContentType.APPLICATION_JSON)
            .execute().returnContent().asString();
    log.info("Call bank API {} url:{} param:{} cost:{}ms", bankAPI.desc(), bankAPI.url(), param, System.currentTimeMillis() - begin);
    return result;
}

Using reflection, the method gathers annotated fields, orders them, formats each according to its type, pads them correctly, adds the MD5 signature, and sends the request, eliminating duplicated code and reducing bugs.

Application in Code

@BankAPI(url = "/bank/createUser", desc = "Create User API")
@Data
public class CreateUserAPI extends AbstractAPI {
    @BankAPIField(order = 1, type = "S", length = 10)
    private String name;
    @BankAPIField(order = 2, type = "S", length = 18)
    private String identity;
    @BankAPIField(order = 4, type = "S", length = 11)
    private String mobile; // order must follow API spec
    @BankAPIField(order = 3, type = "N", length = 5)
    private int age;
}

@BankAPI(url = "/bank/pay", desc = "Payment API")
@Data
public class PayAPI extends AbstractAPI {
    @BankAPIField(order = 1, type = "N", length = 20)
    private long userId;
    @BankAPIField(order = 2, type = "M", length = 10)
    private BigDecimal amount;
}

By annotating POJOs and invoking remoteCall, developers obtain a clean, maintainable way to construct fixed‑length request strings for the bank's APIs.

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.

JavaReflectionAPIannotationsstring formatting
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.