Sebastiaan van den Broek
Sebastiaan van den Broek

Reputation: 6331

Using validation in Spring to restrict ZonedDateTime field to 3 digits at millisecond time

I have an input DTO in a Spring MVC request that I want to validate, specifically the ZonedDateTime field should at most contain 3 digits at millisecond level, i.e. it cannot be the full nanoseconds precision. Other than that the input request should follow the ISO datetime format. I could make the field a String and then just restrict it with a regex but I prefer to keep it as an ZonedDateTime so I don't need to parse it again.

The object looks like this:

@Data
public class MeasurementDTO {

    private ZonedDateTime date;
    @Digits(integer = 20, fraction = 8) private BigDecimal value;

}

And this is a nested DTO where the parent object comes in as a @RequestBody with the @Valid annotation.

I've tried @JsonFormat but I can't seem to restrict the millisecond part. Is there any way to do this or should I just parse it myself as a String and then deal with it? Or even just leave it at ZonedDateTime and then check the nanosecond component to see if it's there in a custom validator?

Thanks to Tim's answer I remembered Java Dates don't have more precision than millis anyway, so I updated the question to use ZonedDateTimes which do have up to nanosecond precision. I do want to be able to give the user a warning if they do attempt to pass more precision, if using Date this information would just get swallowed.

Upvotes: 0

Views: 1919

Answers (2)

Sebastiaan van den Broek
Sebastiaan van den Broek

Reputation: 6331

I did this the hard way with a custom validator. I am still welcoming answers that do this in a more concise way though.

Here's my solution:

I added an annotation @ValidMeasurementInput

@Documented
@Constraint(validatedBy = MeasurementValidator.class)
@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface ValidMeasurementInput {

    String message() default "Invalid measurement input";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

And implemented a custom validator

public class MeasurementValidator implements ConstraintValidator<ValidMeasurementInput, MetricsDTO> {

    @Override
    public boolean isValid(MetricsDTO metricsDTO, ConstraintValidatorContext context) {
        ...
    }
}

Somewhere in this class among some other validations is this code:

   int nano = measurementDTO.getDate().getNano();
   int remainderAfterMillis = nano % 1000000;
   if (remainderAfterMillis != 0)
       valid = false;

And of course I added the @ValidMeasurementInput to my DTO.

Upvotes: 0

Tim Biegeleisen
Tim Biegeleisen

Reputation: 521437

You may not consider this to be a complete answer, but java.util.Date only stores precision up to milliseconds, and not beyond that. See Jon Skeet's answer here, or read the source code for CalendarDate, which reveals that it has nothing beyond millisecond storage.

So, there is no sense in restricting a Date to millisecond precision with Hibernate validation, because the type itself already carries this restriction.

Upvotes: 1

Related Questions