Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In the tutorial Java Bean Validation Basics, we saw how to apply basic javax validation to various types, and in this tutorial, we'll focus on using javax validation with BigDecimal.

2. Validating BigDecimal Instances

Unfortunately, with BigDecimal, we can't use the classic @Min or @Max javax annotations.

Luckily, we have a dedicated set of annotations for working with them:

  • @DecimalMin

  • @Digits

  • @DecimalMax

BigDecimal is the first choice for financial calculation because of its high precision.

Let's see our Invoice class, which has a field of type BigDecimal:

public class Invoice {

    @DecimalMin(value = "0.0", inclusive = false)
    @Digits(integer=3, fraction=2)
    private BigDecimal price;
    private String description;

    public Invoice(BigDecimal price, String description) {
        this.price = price;
        this.description = description;
    }
}

2.1. @DecimalMin

The annotated element must be a number whose value is higher or equal to the specified minimum. @DecimalMin has an attribute inclusive that indicates whether the specified minimum value is inclusive or exclusive.

2.2. @DecimalMax

@DecimalMax is the counterpart of @DecimalMin. The annotated element must be a number whose value is lower or equal to the specified maximum. @DecimalMax has an inclusive attribute that specifies whether the specified maximum value is inclusive or exclusive.

Also, @Min and @Max accept long values only. In @DecimalMin and @DecimalMax, we can specify the value in string format, which can be of any numeric type.

2.3. @Digits

In many cases, we need to validate the number of digits in the integral part and fraction part of a decimal number.

The @Digit annotation has two attributes, integer and fraction, for specifying the number of allowed digits in the integral part and fraction part of the number.

As per the official documentation, integer allows us to specify the maximum number of integral digits accepted for this number. But this is true only for non-decimal numbers. For decimal numbers, it checks the exact number of digits in an integral part of the number. We will see this in our test case.

Similarly, the fraction attribute allows us to specify the maximum number of fractional digits accepted for this number.

2.4. Test Cases

Let's see these annotations in action.

First, we'll add a test that creates an Invoice with an invalid price according to our validation, and checks that the validation will fail:

public class InvoiceUnitTest {

    private static Validator validator;

    @BeforeClass
    public static void setupValidatorInstance() {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    @Test
    public void whenPriceIntegerDigitLessThanThreeWithDecimalValue_thenShouldGiveConstraintViolations() {
        Invoice invoice = new Invoice(new BigDecimal(10.21), "Book purchased");
 
        Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
 
        assertThat(violations.size()).isEqualTo(1);
        violations.forEach(action -> assertThat(action.getMessage())
                .isEqualTo("numeric value out of bounds (<3 digits>.<2 digits> expected)"));
    }
}

Now let's check the validation with a correct price that's an integer value:

@Test
public void whenPriceIntegerDigitLessThanThreeWithIntegerValue_thenShouldNotGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal(10), "Book purchased");
 
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
 
    assertThat(violations.size()).isEqualTo(0);
}

If we set a price with more than 3 digits in the integral part, we should see a validation error:

@Test
public void whenPriceIntegerDigitGreaterThanThree_thenShouldGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal(1021.21), "Book purchased");
 
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
 
    assertThat(violations.size()).isEqualTo(1);
    violations.forEach(action -> assertThat(action.getMessage())
      .isEqualTo("numeric value out of bounds (<3 digits>.<2 digits> expected)"));
}

A price equal to 000.00 should also a constraint validation:

@Test
public void whenPriceIsZero_thenShouldGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal(000.00), "Book purchased");
 
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
 
    assertThat(violations.size()).isEqualTo(1);
    violations.forEach(action -> assertThat(action.getMessage())
      .isEqualTo("must be greater than 0.0"));
}

Finally, let's the case with a price that's greater than 0:

@Test
public void whenPriceIsGreaterThanZero_thenShouldNotGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal(100.50), "Book purchased");
 
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
 
    assertThat(violations.size()).isEqualTo(0);
}

3. Conclusion

In this article, we saw how to use javax validation for BigDecimal.

All code snippets can be found over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!