Backend Development 9 min read

Solve Long Precision Loss in Spring Boot 3 with Jackson and Custom Annotations

This article demonstrates how to handle Java Long precision loss in Spring Boot 3 front‑end displays, configures Jackson to serialize Long as String, uses @JsonValue for single‑property serialization, and creates custom annotations with a SensitiveSerializer for data masking, complete with code examples and screenshots.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Solve Long Precision Loss in Spring Boot 3 with Jackson and Custom Annotations

In previous articles we introduced extensive Jackson usage in Spring Boot. This article focuses on two main topics: Long precision loss in the front‑end and @JsonValue serialization of single property values, plus the creation of custom annotations for sensitive data masking.

1. Introduction

The following topics are covered:

Long type precision loss in the front‑end

@JsonValue serialization of single property values

Custom annotation application

2. Practical Cases

2.1 Long Precision Loss

When JavaScript handles large numbers such as Java Long , precision can be lost because JavaScript uses IEEE‑754 64‑bit floating‑point numbers, which cannot accurately represent integers larger than 2^53‑1 . The following controller returns a 17‑digit Long value:

<code>@RestController
@RequestMapping("/longs")
public class LongController {

  @GetMapping("")
  public Map&lt;String, Object&gt; getData() {
    return Map.of("code", 0, "data", 123456789012345678L);
  }
}
</code>

Directly accessing the endpoint in a browser shows the correct value, but the network response contains the truncated number. An Ajax request also returns the incorrect value:

<code>function getData() {
  axios.get('http://localhost:8080/longs')
    .then(resp => {
      console.log(resp.data);
    }).catch(error => {
      console.log(error);
    });
}
</code>

To prevent this issue, configure Jackson to serialize all Long values as String globally:

<code>@Component
public class PackMapperCustomizer implements Jackson2ObjectMapperBuilderCustomizer {

  @Override
  public void customize(Jackson2ObjectMapperBuilder builder) {
    builder.serializerByType(Long.class, ToStringSerializer.instance);
  }
}
</code>

If only specific fields need conversion, annotate them with @JsonSerialize(using = ToStringSerializer.class) :

<code>public class User {

  @JsonSerialize(using = ToStringSerializer.class)
  private Long id;
  private String name;
  // getters, setters
}
</code>

2.2 @JsonValue Serialization of Single Property Values

The @JsonValue annotation marks a method or field whose value will be used as the JSON representation of the entire object. For example, an enum:

<code>public enum PaymentStatus {
  NO_PAY(0, "未支付"), PAID(1, "已支付");

  private Integer code;
  private String desc;
  // constructor, getters, setters
}
</code>

When the enum is returned from a controller, the default JSON output is the enum name. Adding @JsonValue to a field makes that field the JSON value:

<code>@JsonValue
private String desc;
</code>

Applying @JsonValue to a regular bean field yields the same effect:

<code>public class User {

  @JsonSerialize(using = ToStringSerializer.class)
  private Long id;

  @JsonValue
  private String name;
}
</code>

2.3 Custom Annotation for Sensitive Data Masking

To mask sensitive fields such as ID cards or phone numbers, define a custom annotation using @JacksonAnnotationsInside and a serializer:

<code>@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
  int start() default 0;
  int end() default 0;
  String mask() default "*";
}
</code>

The corresponding serializer implements the masking logic:

<code>public class SensitiveSerializer extends JsonSerializer&lt;String&gt; implements ContextualSerializer {

  private Sensitive sensitive;

  @Override
  public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    String val = value;
    if (sensitive != null && StringUtils.hasLength(val)) {
      String m = sensitive.mask();
      int start = sensitive.start();
      int end = sensitive.end();
      int totalLength = value.length();
      // masking algorithm omitted for brevity
    }
    gen.writeString(val);
  }

  @Override
  public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
    sensitive = property.getAnnotation(Sensitive.class);
    return this;
  }
}
</code>

Use the annotation on fields that require masking:

<code>public class User {

  @JsonSerialize(using = ToStringSerializer.class)
  private Long id;
  private String name;

  @Sensitive(start = 6, end = 4)
  private String idCard;

  @Sensitive(start = 4, end = 3)
  private String phone;
  // getters, setters
}
</code>

The front‑end now displays masked values for the annotated fields.

Overall, the article provides practical solutions for Long precision handling, single‑property JSON serialization, and custom data masking in Spring Boot 3 applications.

Spring BootCustom AnnotationJacksondata maskingJSON serializationLong precision
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.