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, as they only accept long values.

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 greater than 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.

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 whenMoreThanThreeIntegerDigits_thenShouldGiveConstraintViolations() {
        Invoice invoice = new Invoice(new BigDecimal("1021.21"), "Book purchased");
        Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
        assertThat(violations).hasSize(1);
        assertThat(violations)
            .extracting("message")
            .containsOnly("numeric value out of bounds (<3 digits>.<2 digits> expected)");
    }
}

Now let’s check the validation with the correct price:

@Test
public void whenLessThanThreeIntegerDigits_thenShouldNotGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal("10.21"), "Book purchased");
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
    assertThat(violations).isEmpty();
}

In a similar way, let’s see how validation works for the fractional part:

@Test
public void whenTwoFractionDigits_thenShouldNotGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal("99.99"), "Book purchased");
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
    assertThat(violations).isEmpty();
}

@Test
public void whenMoreThanTwoFractionDigits_thenShouldGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal("99.999"), "Book purchased");
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
    assertThat(violations).hasSize(1);
    assertThat(violations)
        .extracting("message")
        .containsOnly("numeric value out of bounds (<3 digits>.<2 digits> expected)");
}

A price equal to 0.00 should violate our constraint:

@Test
public void whenPriceIsZero_thenShouldGiveConstraintViolations() {
    Invoice invoice = new Invoice(new BigDecimal("0.00"), "Book purchased");
    Set<ConstraintViolation<Invoice>> violations = validator.validate(invoice);
    assertThat(violations).hasSize(1);
    assertThat(violations)
        .extracting("message")
        .containsOnly("must be greater than 0.0");
}

Finally, let’s see the case with a price greater than zero:

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

3. Conclusion

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

All code snippets can be found over on GitHub.