Spring Boot Custom Validation

Table of contents:

  1. Overview
  2. Constraint types
  3. Start a sample project
  4. Validation of nested objects
  5. Custom Validation
  6. Custom Validation Error Message in Spring Boot
  7. Testing of the REST Controller

Overview

Bean Validation API defines a powerful mechanism, which ensures validation of data and can be applied for incoming data or when data is being persisted into a database, using JPA. Bean Validation API is defined by specification JSR 380.

Spring provides seamless integration with the Validation API and lets us concentrate on business logic when adding custom constraints. So, to add custom validations, we just have to implement a couple of interfaces, which will be discussed below.

Besides a robust validation mechanism, it’s also important to ensure a convenient and clear response to a client-side in case of any errors or constraint violations. Usually, we are talking about a web application or a mobile application, which takes the response and display appropriate errors to the end-user.

By default, in Spring, all validation errors result in HTTP 400 response, with a default message, which can contain an exhaustive description of errors. In some cases, we need a custom response. Let’s consider an example (Fig. 1). This is how we are going to interact with the validation layer in the current tutorial.

Fig. 1. Example of HTTP response in case of wrong input data

In the example above, the user enters the wrong data in a form and press submit button. Web application sends data (in our case – JSON) to REST API, where the validation layer validates inputs and generates appropriate errors. REST Controller, in turn, intercepts errors and translates those to readable JSON. Web Application takes JSON and displays errors on the page, so the user understands, which data was entered incorrectly.

Constraint types

Validation in Java comes down to validation of POJOs or Java Beans. And here we can shape out two types of constraints:

  1. Field constraints, which gets applied to a particular field (e.g. email, date)
  2. Structural constraints or entity-level constraint, which gets applied to the entire Object and usually validates two and more fields tied logically. For instance, date range applied to start date and end date. Validates that start date is before end date.

Start the sample project

We are going to implement an example, discussed above. Let’s create a new project. Will be used:

  1. Java 11
  2. Gradle 5.2.1 with Groovy DSL
  3. Spring boot 2.2.6.RELEASE

Dependencies section of build.gradle will look like below. Check full file contents here.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'

	annotationProcessor("org.projectlombok:lombok:1.18.4")
	implementation("org.projectlombok:lombok:1.18.4")
}

In the tutorial, we are using sample classes OrderForm and DeliveryAddress, which validation will be applied for. Let’s create a class and add some basic constraints.

@Data
public class OrderForm {
    @NotEmpty
    private String productId;
    @NotEmpty
    @Email
    private String userEmail;
    @NotEmpty
    private String name;
    @NotNull
    private DeliveryAddress deliveryAddress;
    @NotNull
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date deliveryAfter;
    @NotNull
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date deliveryBefore;
}
@Data
public class DeliveryAddress {
    @NotEmpty
    private String city;
    @NotEmpty
    private String line;
    @NotEmpty
    private String zip;
}
  • NotEmpty – marks the field as one, which should be filled out. Also applicable to collections.
  • Email – value should be a valid email address.
  • NotNull – value should be specified.

Add Spring REST Controller with single method submitOrder(..). In case of success, just for sake of simplicity, we will return JSON with fake order Id. To mark a request as one, which should be validated, we have to add an annotation @Valid before argument:

@RestController
public class OrderRestController {

    @PostMapping(path = "/orders", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public PlacedOrder submitOrder(@Valid @RequestBody OrderForm orderForm) {
        return new PlacedOrder("order_123");
    }
}

Now we can launch application and hit endpoint.

Validation of nested objects

In the class OrderForm, deliveryAddress is defined by another class DeliveryAddress. Although we marked field deliveryAddress as @NotNull, the internal state of this field will not be validated (validation of fields city, line, zip will be skipped).

Here we have to add an annotation @Valid for deliveryAddress, so validator will validate the content of deliveryAddress as well.

@Valid
@NotNull
private DeliveryAddress deliveryAddress;

Custom Validation

In order to define custom validation mechanism, we should:

  • add custom java annotation, which defines constrain and its metadata
  • implement interface javax.validation.ConstraintValidator – validation logic

We will implement two constraints:

  • Field constraintValidProductId, which defines format of product id in form PROD-0000, where PROD- is fixed prefix, and 0000 – any number.
  • Entity-level constraintValidDeliveryDateRange. New constraint defines simple rule: deliveryBefore should go before deliveryAfter.

Field-level constraint

Let’s add new annotation:

@Constraint(validatedBy = ProductIdValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidProductId {

    String message() default "Wrong product Id";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Target specified as either METHOD or FIELD. It means, that annotation is applicable to class field or method.

Validation Rules:

  • productId value should not be null
  • productId value should match regular expression PROD-\\d{4}
public class ProductIdValidator implements ConstraintValidator<ValidProductId, String> {

    @Override
    public boolean isValid(String productId, ConstraintValidatorContext context) {
        return productId != null && productId.matches("PROD-\\d{4}");
    }
}

Now, annotation @ValidProductId can be added to the code:

@ValidProductId
private String productId;

By the way, since we checked productId on null in the validator, we can remove @NotEmpty annotation.

Entity-level constraint

Add new annotation:

@Constraint(validatedBy = DeliveryDateRangeValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidDeliveryDateRange {

    String message() default "Wrong delivery date range";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Target is specified as TYPE. Unlike @ValidProductId, new annotation is applicable to a class only.

Validation rules:

  • deliveryBefore should not be null
  • deliveryAfter should not be null
  • deliveryAfter < deliveryBefore

Appropriate validator:

public class DeliveryDateRangeValidator implements ConstraintValidator<ValidDeliveryDateRange, OrderForm> {

    @Override
    public boolean isValid(OrderForm value, ConstraintValidatorContext context) {
        boolean isValid = true;
        Date deliveryAfter = value.getDeliveryAfter();
        Date deliveryBefore = value.getDeliveryBefore();
        
        if (deliveryAfter == null || deliveryBefore == null) {
            isValid = false;
        } else if (deliveryAfter.getTime() > deliveryBefore.getTime()) {
            isValid = false;
        }
        return isValid;
    }
}

Now we can launch application and send test request, like this:

{
   "productId":"PROD-5676",
   "userEmail":"mike@mail.com",
   "name":"Mike",
   "deliveryAddress":{
      "city":"Los Angeles",
      "line":"Aviation Blvd.",
      "zip":"4056"
   },
   "deliveryAfter":"2020-06-01",
   "deliveryBefore":"2020-06-05"
}

Custom Validation Error Message in Spring Boot

As it was mentioned above, sometimes it’s useful to ensure customized response in case of validation failure. Let’s say, we want to make an error message clear and convenient for the client, which uses API. So, the message can be easily reflected in user-friendly representation and either web-application or mobile-client can easily tie validation error message to a particular field.

So, let’s define format of our custom Validation Error Message.

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ValidationError {
    private HttpStatus httpStatus;
    private Map<String, List<ValidationErrorEntry>> errors;
}
  • httpStatus – BAD_REQUEST
  • errors – map, where key – field name, value – list of validation errors. Each error is defined by class ValidationErrorEntry
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class ValidationErrorEntry {
    private String code;
    private String message;
}

For instance, if productId contains a wrong value, validation error message, returned by API, will be:

{
   "httpStatus":"BAD_REQUEST",
   "errors":{
      "productId":[
         {
            "code":"ValidProductId",
            "message":"Wrong product Id"
         }
      ]
   }
}

Now we have to intercept validation errors and convert it into ValidationError, defined above. Method handleMethodArgumentNotValid() of ResponseEntityExceptionHandler handles validtion errors and converts it into default representation of HTTP-response.

We can override the method handleMethodArgumentNotValid and provide new validation errors processing logic as well as customize the HTTP-response.

In order to make Spring Boot automatically bootstrap new class, it should be marked with annotation @ControllerAdvice.

@ControllerAdvice
public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {

    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex,
            HttpHeaders headers,
            HttpStatus status,
            WebRequest request) {

        Map<String, List<ValidationErrorEntry>> errors = new HashMap<>();
        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
            String field = error.getField();
            if (!errors.containsKey(field)) {
                errors.put(field, new ArrayList<>());
            }
            errors.get(field).add(new ValidationErrorEntry(error.getCode(), error.getDefaultMessage()));
        }

        for (ObjectError error : ex.getBindingResult().getGlobalErrors()) {
            String objectName = error.getObjectName();
            if (!errors.containsKey(objectName)) {
                errors.put(objectName, new ArrayList<>());
            }
            errors.get(objectName).add(new ValidationErrorEntry(error.getCode(), error.getDefaultMessage()));
        }

        ValidationError validationError = new ValidationError(HttpStatus.BAD_REQUEST, errors);
        return handleExceptionInternal(ex, validationError, headers, validationError.getHttpStatus(), request);
    }
}

In line 12 of the code above, ex.getBindingResult().getFieldErrors() returns field-level errors. An expression in line 20 ex.getBindingResult().getGlobalErrors() returns entity-level errors.

Testing of the REST Controller

Let’s add the Integration test to make sure, that the validation mechanism works properly and expected validation error appears in so the case.

The test method checks, that for the wrong email address posted, API returns an appropriate message.

@WebMvcTest
@AutoConfigureMockMvc
public class OrderRestControllerIntegrationTest {

    @Autowired
    private OrderRestController orderRestController;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnBadRequestWhenPostedWrongEmailAddress() throws Exception {
        String request = "{" +
                "\"productId\": \"PROD-4565\"," +
                "    \"userEmail\": \"wrong email value\"," +
                "    \"name\": \"A\"," +
                "    \"deliveryAddress\": {" +
                "       \"city\": \"Los Angeles\"," +
                "       \"line\": \"Aviation Blvd.\"," +
                "       \"zip\": \"4056\" " +
                "    }," +
                "    \"deliveryAfter\": \"2020-06-01\"," +
                "    \"deliveryBefore\": \"2020-06-05\"" +
                "}";
        mockMvc.perform(MockMvcRequestBuilders.post("/orders")
                .content(request)
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isBadRequest())
                .andExpect(MockMvcResultMatchers.jsonPath(
                        "$.errors.userEmail[*].message",
                        Is.is(Lists.list("must be a well-formed email address"))))
                .andExpect(MockMvcResultMatchers.content()
                        .contentType(MediaType.APPLICATION_JSON));
    }
}

Links:

Source code – https://github.com/dicelogics/validation-api-tutorial

1706
Leave a Reply

avatar
  Subscribe  
newest oldest most voted
Notify of
ScottNes
Guest

Play for free on cool parcel of land https://www.myadultonlinegames.com/category/premium-adult-games in adult games

ZenPars5
Guest

гадалка яндекс дзен ру безграмотный фантазер наталья яндекс дзен читать 1 класс вечер у камина дзен яндекс циан александр дедушка яндекс дзен главная страница вот я фрося яндекс дзен армения бальзам для души яндекс дзен читать полностью как отключить дзен в яндексе на компьютере яндекс дзен стакан молока рассказы дзен яндекс свадьба https zen yandex яндекс дзен почему мало просмотров на яндекс дзен гадалка яндекс дзен woman forever александр дедушка яндекс дзен вера с как отключить яндекс дзен в браузере мозилла осенняя любовь яндекс дзен все краски жизни яндекс дзен читать цветок и камень сколько платит яндекс дзен за просмотры… Read more »

Donaldhru
Guest
Donaldhru

Приветствую Вас товарищи
Where is moderator??
It is about advertisement on your website.
Thank.
плиткорез супер про 900

Donalduuv
Guest
Donalduuv

Доброго времени суток товарищи
Where is administration?
It is important.
Regards.
плиткорез hammer

BuyEssayOnline
Guest

Social geography essay topics .

Proceed to Order!!! https://thinfi.com/039dh

UYhjhgTDkJHVy

Jasonsat
Guest

https://geni.us/qX4pKE leather backpack for females

JzuAB
Guest

Drugs information sheet. Generic Name.
can you buy lexapro in US
Best about medicines. Get information here.

BobbyBum
Guest
AliceLyday
Guest

Hi! I’m Alice from Russia. I am looking for a sponsor. I want to find a grown man. Come to my profile: http://datingforkings.club/ I am 18 years old. I `m A virgin.

BuyEssayOnline
Guest

Popular problem solving editor for hire for masters .

Order NOW!!! https://bookmarking.stream/story.php?title=the-ultimate-guide-to-essay-shark#discuss

UYhjhgTDkJHVy

Durekbip
Guest

Regards, Good information. how to write effective essays writing dissertations what is a dissertation paper

JosephPep
Guest

Одними из основных направлений нашей деятельности являются кровельные работы, демонтаж ветхих крыш и ремонт старых. Все наши мастера граждане Российской Федерации, имеют большой опыт работы в кровельном деле. Также они всегда трезвые, вежливы и аккуратно относятся к имуществу заказчика.
тел.+7 (925) 576-88-89
Наш сайт

KevvinsKymn
Guest
KevvinsKymn

loli*ta gi*rl fu*ck c*p pt*hc

https://xor.tw/4pgec

BuyEssayOnline
Guest

Research paper on pediatric nurse .

Proceed to Order!!! https://opensourcebridge.science/wiki/The_Definitive_Guide_to_Payday_Loans_Online

UYhjhgTDkJHVy

DarnellCak
Guest

I think, that you commit an error. I can prove it. Write to me in PM, we will talk.

Carlostaign
Guest

Happens… Such casual concurrence

Kennethpinee
Guest

I am sorry, that I interrupt you.
santaclausiscomingout

Dawnlek
Guest
Dawnlek

Best of Ali – https://bit.ly/3hx9fpd

BuyEssayOnline
Guest

The raven setting essay .

Order NOW!!! http://q2a.sydt.com.tw/index.php?qa=user&qa_1=essaytyper-com

UYhjhgTDkJHVy

BuyEssayOnline
Guest

Cheap thesis ghostwriting site for mba .

Proceed to Order!!! https://www.nyticketdeals.com/members/studybay-com/activity/188679/

UYhjhgTDkJHVy

GridGroupspuby
Guest

Приглашаем посетить и зарегистрироваться на новом Форуме о заработках в интернете. Создавайте любые темы о бизнесе, размещение ссылок НЕ запрещено. Форум новый, успейте быть первыми в продвижении Вашего бизнеса. https://mine-plex-bot.blogspot.com/

BuyEssayOnline
Guest

Essay my favourite film .

Proceed to Order!!! https://ondashboard.win/story.php?title=essaytyper-essaytyper-com#discuss

UYhjhgTDkJHVy

appandano
Guest

Сайт интим услуг по России http://ccly.xyz/AvlR

Hot Celebs Fut
Guest
Hot Celebs Fut
Donaldpwd
Guest
Donaldpwd

Привет дамы и господа
Where is moderator??
I’ts important.
Regards.
тепловизор flir ps24 цена

Caydemox
Guest

Наша компания– предлагает пероксид водорода для очистки водоемов и бассейнов.

Deweygom
Guest

A best hookup app. Where can you find a pretty milf for quick sex.
The link is http://nekzaheq.cf

BuyEssayOnline
Guest

Resume format for b tech students .

Proceed to Order!!! https://www.sitiosenparaguay.com/author/edubirdiecom/

UYhjhgTDkJHVy

Lerraphamy
Guest
Lerraphamy

Привет! Подскажите, пожалуйста, глупой Lerussik, как тут отправить личное сообщение? Очень надо! Спасибо
Hi! Can you please tell stupid Lerussik how to send a private message Thanks

AlenaViamy
Guest

Весь вечер познавал данные инета, при этом к своему восторгу увидел важный вебсайт. Гляньте: https://fitnessmarket.ua/category/sportivaksessuary-ecofit . Для меня этот вебсайт оказал яркое впечатление. Всего наилучшего!

Alinablide
Guest

Весь вечер осматривал материалы инета, при этом к своему удивлению заметил отличный веб-сайт. Ссылка на него: временный номер телефона . Для меня этот веб-сайт оказался очень нужным. До свидания!

BuyEssayOnline
Guest

Resume maker professional deluxe v16 .

Proceed to Order!!! http://proshivki-all.ru/user/schoolhelp83/

UYhjhgTDkJHVy

Allamaync
Guest

Почти час просматривал материалы интернет, случайно к своему удивлению увидел хороший вебсайт. Вот посмотрите: https://7sim.net/ru . Для моих близких вышеуказанный ресурс оказался весьма важным. Всех благ!

ivyrj2
Guest

Enjoy daily galleries
http://eroticporncomixsierra.city.amandahot.com/?bailee
streaming porn blondes porn tube mature boys free download quality porn hispanic lesbians porn sites stream porn porntube

AnastasiyaToirl
Guest

Полчаса исследовал содержимое интернет, и неожиданно к своему восторгу обнаружил хороший веб-сайт. Вот смотрите: https://fitnessmarket.ua/category/begovye-dorozhki . Для нас этот ресурс произвел радостное впечатление. Хорошего дня!

coletteyp16
Guest

Young Heaven – Naked Teens & Young Porn Pictures
http://pine.lake.danexxx.com/?nora
hot teen porn pic teen whore porn philipion porn archive bbw mercy porn african slave porn

AnfisaDooNe
Guest

Несколько дней назад разглядывал материалы интернет, неожиданно к своему восторгу заметил четкий вебсайт. Вот гляньте: купить номер для регистрации . Для моих близких этот вебсайт оказался довольно привлекательным. До свидания!

Jimmymah
Guest

Hello,

Download Music Scene Releases: https://0daymusic.org
Server’s capacity 186 TB Music
Support for FTP, FTPS, SFTP, HTTP, HTTPS.

Best regards, Jimmy

FrankTusty
Guest

Greetings from Los angeles! I’m bored at work so I decided to browse your site on my iphone during lunch break. I love the info you provide here and can’t wait to take a look when I get home. I’m shocked at how fast your blog loaded on my cell phone .. I’m not even using WIFI, just 3G .. Anyways, awesome blog!

Vasilisahek
Guest

На днях анализировал материалы интернет, и к своему удивлению заметил неплохой веб-сайт. Вот ссылка: https://7sim.net/ru . Для моих близких этот ресурс оказал незабываемое впечатление. Хорошего дня!

Paulsob
Guest

Ежемесячно до 40% на криптовалюте!
Это просто и легко!
Бот поможет и все покажет

BuyEssayOnline
Guest

Dissertation sur l39amour philosophie .

Order NOW!!! https://s.id/

UYhjhgTDkJHVy

Jimosceway
Guest

Amazing write ups. Thanks a lot.

professional letter writing services
how to write a thesis statement

Angelinapiolo
Guest

Полчаса анализировал содержание инет, и неожиданно к своему восторгу заметил полезный ресурс. Посмотрите: https://fitnessmarket.ua/category/begovye-dorozhki . Для моих близких этот веб-сайт произвел радостное впечатление. Всего доброго!

Valentinashoni
Guest

Целый вечер мониторил содержание инета, при этом к своему удивлению обнаружил отличный веб-сайт. Смотрите: смс на виртуальный номер . Для моих близких этот веб-сайт оказался довольно оригинальным. Всего хорошего!

erinba60
Guest

Sexy photo galleries, daily updated pics
http://malayalampornkenbridge.alypics.com/?alexys
what are good porn websites yahoo gay porn torrents saco retro german porn movies blaqck shemale porn porn ashwarya site

Veraplark
Guest

Все утро познавал материалы инет, при этом к своему восторгу увидел прекрасный веб-сайт. Вот: https://7sim.net/ru . Для нас этот ресурс произвел хорошее впечатление. Удачи!

latashalb69
Guest

Scandal porn galleries, daily updated lists
http://cartersvilleadultgoofyporn.danexxx.com/?aracely
overcoming addiction to porn porn addiction destroys marriage latina 8 porn slutload houssewifes doing amature porn top 100 porn stars of 2010

vondanf69
Guest

Young Heaven – Naked Teens & Young Porn Pictures
http://nittanyboyporntube.miyuhot.com/?sydnie
full porn vdeios free hermaphradite porn vids free sott porn long porn thumbs young teen porn sex

Annaelems
Guest

Много познавал содержание интернет, и к своему восторгу увидел познавательный вебсайт. Посмотрите: https://fitnessmarket.ua/category/orbitreki . Для моих близких данный ресурс оказал хорошее впечатление. Всего доброго!