Want to give custom annotation at RequestBody Pojo class - spring

We can give #valid annotation but I want to give custom annotation at #RequestBody.
Use case: In my person Pojo class, I have two fields firstname and lastname. so I want to validate pojo class in that way that if user has given value for any field (like given for lastname) then it's good. but Both field should not be empty. User should give value for at least one field (it is either or condition)
My Pojo class:
class Person {
private String firstName;
private String lastName;
}
we can't give #NotNull for both fields. so I want to give custom annotation at the class level.
In that validator, I will check both fields and will send the proper error message to User.

You can try Custom ConstraintValidator, simple example #ValidatePerson:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#RestController
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostMapping
public Person doSomeThingPerson(#Validated #RequestBody Person person) {
return person;
}
#ValidatePerson
public static class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = {PersonValidator.class}) // you can use multiply validators
public #interface ValidatePerson {
String message() default "Invalid Person, firstName and lastName can't be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class PersonValidator implements ConstraintValidator<ValidatePerson, Person> {
#Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
return person.getFirstName() != null || person.getLastName() != null;
}
}
}
If firstName and lastName both null then:
{
"timestamp": 1560456328285,
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"ValidatePerson.person",
"ValidatePerson"
],
"arguments": [
{
"codes": [
"person.",
""
],
"arguments": null,
"defaultMessage": "",
"code": ""
}
],
"defaultMessage": "Invalid Person, firstName and lastName can't be null",
"objectName": "person",
"code": "ValidatePerson"
}
],
"message": "Validation failed for object='person'. Error count: 1",
"path": "/"
}
Also you can customize exception with #ControllerAdvice

Related

Bean validator: validate nested object while adding a prefix to its error messages

I'm having a problem where, when I have multiple nested beans of the same type, the returned message may end up being the same, which can confuse the user:
Minimal example (the real beans have lots of fields):
class A {
#NotBlank("Name is obligatory.")
String name;
#NotBlank("Address is obligatory.")
String name;
}
class B {
#Valid
A origin;
#Valid
A destination;
}
If I run B against the validator with blank names, it will always return "Name is obligatory.", no matter if it comes from the origin or from the destination. I know that the error message comes with the field names, but that information, by itself, is not very useful for the end user.
Is there some annotation that validates the nested beans similarly to what #Valid does, but adding a prefix, so that instead of saying "Name is obligatory.", it would say either "Original person: Name is obligatory." or "Destination person: Name is obligatory."?
I couldn't find any out-of-the box way to implement what was needed, so I had to implement a custom constraint:
PrefixConstraint.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = PrefixConstraintValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface PrefixConstraint {
String message() default "Prefix missing";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
PrefixConstraintValidator.java
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
public class PrefixConstraintValidator implements ConstraintValidator<PrefixConstraint, Object> {
private PrefixConstraint constraint;
#Override
public void initialize(PrefixConstraint constraintAnnotation) {
this.constraint = constraintAnnotation;
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(value, this.constraint.groups());
context.disableDefaultConstraintViolation();
for (ConstraintViolation<Object> violation : violations) {
context
.buildConstraintViolationWithTemplate(this.constraint.message() + ": " + violation.getMessage())
.addPropertyNode(violation.getPropertyPath().toString())
.addConstraintViolation();
}
return violations.isEmpty();
}
}
Usage example:
class A {
#NotBlank("Name is obligatory.")
String name;
#NotBlank("Address is obligatory.")
String name;
}
class B {
#PrefixConstraint(message = "Original person")
A origin;
#PrefixConstraint(message = "Destination person")
A destination;
}
Now, if the names are left blank, the validator will return the message: "Original person: Name is obligatory" and "Destination person: Name is obligatory".

Spring Boot Repository Returns the same value

Controller.java
#RestController
#RequestMapping(value = "/v2/customers/")
#Api(value = "account", description = "Operations pertaining to account operations")
public class AccountController {
private final AccountService accountService;
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
#GetMapping(value = "accounts/{CIFs}")
public Response getAccountByCIF(#PathVariable String CIFs) {
return Response.ok().setData(accountService.findByCustomerNumberIn(CIFs));
}
}
AccountRepository.java
#Repository
public interface AccountRepository extends JpaRepository<AccountView, String> {
List<AccountView> getByCustomerNumber(String cif);
List<AccountView> getByCustomerNumberIn(List<String> cifList);
}
AccountService.java
public interface AccountService {
List<AccountDto> findByCustomerNumber(String cifList);
List<AccountDto> findByCustomerNumberIn(String cifList);
}
AccountServiceImpl.java
#Service
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
#Autowired
private AccountMapper accountMapper;
public AccountServiceImpl(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
#Override
public List<AccountDto> findByCustomerNumber(String cif) {
List<AccountView> account = accountRepository.getByCustomerNumber(cif);
System.out.println(account);
if (!account.isEmpty()) {
return accountMapper.toAccountDtoList(account).stream().collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
#Override
public List<AccountDto> findByCustomerNumberIn(String cifList) {
List<String> cif = Arrays.asList(cifList.split(","));
List<AccountView> account = accountRepository.getByCustomerNumberIn(cif);
System.out.println(account);
if (!account.isEmpty()) {
return accountMapper.toAccountDtoList(account).stream().collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
When I run the application although the database have different records
it return the same value over again and again .
This is the json outcome. For queried customer there are 6 record in the database, but the endpoint return 6 same record as above.
{
"name": "ACDESC33811018409238803204",
"customer_number": "9238803",
"account_number": "33811018409238803204",
"book_ACC": "33811018409238803204",
"account_branch": {
"branch_number": "204",
"branch_name": "MERKEZI FILIAL"
},
"balance": 88.45,
"currency": "USD",
"status": "NORM",
"class": "33811",
"open_date": "2006-05-26",
"location": null,
"ibanrgd": "AZ16IBAZ33811018409238803204",
"expcat": "GENERAL",
"user_defined_fields": [],
"type": "S",
"number": "2388030001",
"start_tod_limit_date": null,
"end_tod_limit_date": null,
"tod_limit_Interest": null,
"tod_limit_amount": null,
"tod_limit_total_amount": null
},
AccountMapper.java
package az.iba.ms.account.dto.mapper;
import az.iba.ms.account.dto.model.AccountBranchDto;
import az.iba.ms.account.dto.model.AccountDto;
import az.iba.ms.account.dto.model.FieldDto;
import az.iba.ms.account.dto.model.UserDefinedFieldsDto;
import az.iba.ms.account.entity.AccountView;
import org.springframework.stereotype.Component;
import java.sql.Array;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
#Component
public class AccountMapper {
public List<AccountDto> toAccountDtoList(List<AccountView> accountViews) {
return accountViews.stream()
.map(accountView -> toAccountDto(accountView))
.collect(Collectors.toList());
}
public static AccountDto toAccountDto(AccountView account) {
return new AccountDto()
.setName(account.getName())
.setCustomerNumber(account.getCustomerNumber())
.setAccountNumber(account.getAccountNumber())
.setBookACC(account.getBookACC())
.setAccountBranch(new AccountBranchDto( account.getBranchCode(), account.getBranchName()))
.setBalance(account.getBalance())
.setCurrency(account.getCurrency())
.setStatus(account.getStatus())
.set_class(account.get_class())
.setOpenDate(account.getOpenDate())
.setLocation(account.getLocation())
.setIbanRgd(account.getIbanRgd())
.setExpcat(account.getExpcat())
.setUser_defined_fields(null ) // UserDefinedFieldsDto[] = new FieldDto(account.getFieldValue(),account.getFieldName() ))
.setAccType(account.getAccType())
.setNumber(account.getNumber())
.setTodLimitStartDate(account.getTodLimitStartDate())
.setTodLimitEndDate(account.getTodLimitEndDate())
.setTodLimitInterest(account.getTodLimitInterest())
.setTodLimitAmount(account.getTodLimitAmount())
.setTodLimitTotalAmount(account.getTodLimitTotalAmount());
}
}

How can I capture a value that comes through POST to execute a procedure stored in Spring Boot

I am working with Spring Boot, in which I am relatively new, and in this case I am doing a database validation through a Stored Procedue, which I could already solve, the reality is that until now I had done the tests sent the parameter of entry (a mobile number) by GET, but it is required for project reasons, send the parameter through POST, that is to say in a Body with the method:
Method Get
With a Body using the POST Method:
Request
{
"movil":"04242374781";
}
Reponse:
{
"result": "Cliente no encontrado",
"code": "NA22003"
}
mobile is an attribute of the database where the Stored Procedure is executed, for this case it is only necessary to pass that parameter to execute the SP, which returns a response that is not the same object of the database in which it is mobile, then you will see it in the code.
I understand that you can send the parameter for consultation with POST, but in my case try to guide me according to what I got on the internet, but I got an error:
Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported]
My Code
Main class
package com.app.validacion;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
My controller
package com.app.validacion.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.app.validacion.dao.DriverBonificadosRepository;
import com.app.validacion.entity.RespuestaVo;
#RestController
public class DriverBonificadosController {
#Autowired // Inyeccion de Dependecia, en este caso del Respository
private DriverBonificadosRepository dao;
#GetMapping("/service/{movil}")
public RespuestaVo ConsultarMovil(#PathVariable("movil") String movil) {
System.out.println(movil);
return dao.validarClienteBonifiado(movil);
}
/*
the code I was trying to use to send a request in JSON and try to get the mobile parameter,but
I got an error:
Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type
'text/plain' not supported]
/*
* #PostMapping(value = "/service",consumes = "application/json", produces="application/json")
* public RespuestaVo ValidateClient(#RequestBody DriverBonificados driver) {
* System.out.println(driver.getMovil());
* return dao.validarClienteBonifiado(driver.getMovil());
} */
}
My Repository
package com.app.validacion.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.app.validacion.entity.DriverBonificados;
import com.app.validacion.entity.RespuestaVo;
#Repository
public interface DriverBonificadosRepository extends JpaRepository<DriverBonificados, Integer> {
#Query(nativeQuery = true,value = "call ValidacionClienteBonificado(:movil)")
RespuestaVo validarClienteBonifiado(#Param("movil") String pMovil);
}
My Entity
package com.app.validacion.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="DriveBonificados")
public class DriverBonificados {
#Id
private int id;
private String movil;
private String contador;
private Date fecha_driver;
private Date fecha_alta;
private Date fecha_fin;
private Date codigo_transaccion;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMovil() {
return movil;
}
public void setMovil(String movil) {
this.movil = movil;
}
public String getContador() {
return contador;
}
public void setContador(String contador) {
this.contador = contador;
}
public Date getFecha_driver() {
return fecha_driver;
}
public void setFecha_driver(Date fecha_driver) {
this.fecha_driver = fecha_driver;
}
public Date getFecha_alta() {
return fecha_alta;
}
public void setFecha_alta(Date fecha_alta) {
this.fecha_alta = fecha_alta;
}
public Date getFecha_fin() {
return fecha_fin;
}
public void setFecha_fin(Date fecha_fin) {
this.fecha_fin = fecha_fin;
}
public Date getCodigo_transaccion() {
return codigo_transaccion;
}
public void setCodigo_transaccion(Date codigo_transaccion) {
this.codigo_transaccion = codigo_transaccion;
}
}
My Model Response
package com.app.validacion.entity;
public interface RespuestaVo {
String getCode();
String getResult();
}
Nice post, but the (first encountered problem) solution is trivial:
With:
Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported]
and with this postman request, You need to:
postman specific: switch the (request>body) content type from "Text" to "JSON (application/json)"
generally: add a (http) header to Your request like
Content-Type: application/json;...

Issue in custom validation message using messages.properties file in spring boot validation

I am using spring validation to validate the Rest Controller input, I would appreciate if any one can tell me is there a possibility of throwing custom message in case of exception and the custom message should come from properties file.
UserController.java
#CrossOrigin(origins = "http://localhost:3000")
#RequestMapping(
value="/",
method=RequestMethod.POST,
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE
)
public Object[] createUser(#ModelAttribute("user") User user, BindingResult bindingResult) {
new UserValidator().validate(user,bindingResult);
if (bindingResult.hasErrors()) {
return bindingResult.getFieldErrors().toArray();
}
}
UserValidator.java
public class UserValidator implements Validator{
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "user.firstName.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "user.lastName.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "slug", "user.slug.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "user.email.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "user.password.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "phone", "user.phone.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address", "user.address.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "country", "user.country.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gender", "user.gender.empty");
User user = (User) obj;
Pattern pattern = Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
if(!errors.hasErrors()) {
if (!(pattern.matcher(user.getEmail()).matches())) {
errors.rejectValue("email", "user.email.invalid");
}
}
}
}
messages.properties
# messages.properties
user.firstName.empty=Enter a valid first name.
user.lastName.empty = Enter a valid last name.
user.slug.empty = Select gender.
user.phone.empty = Select gender.
user.address.empty = Select gender.
user.country.empty = Select gender.
user.password.empty = Select gender.
user.gender.empty = Select gender.
user.email.empty = Enter a valid email.
user.email.invalid = Invalid email! Please enter valid email.
CustomMessageSourceConfiguration.java
#Configuration
public class CustomMessageSourceConfiguration {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new
ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
}
Browser Response
{codes: ["user.firstName.empty.user.firstName", "user.firstName.empty.firstName",…], arguments:
null,…}
codes: ["user.firstName.empty.user.firstName", "user.firstName.empty.firstName",…]
0: "user.firstName.empty.user.firstName"
1: "user.firstName.empty.firstName"
2: "user.firstName.empty.java.lang.String"
3: "user.firstName.empty"
arguments: null
defaultMessage: null
objectName: "user"
field: "firstName"
rejectedValue: null
bindingFailure: false
code: "user.firstName.empty"
Another Way Of Validation
We can add 4th Parameter As Error Message without using messages.properties file.
public class UserValidator implements Validator{
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName",
"user.firstName.empty","Error Message Here");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName",
"user.lastName.empty","Error Message Here");
...
...
}
}
Another Way Of Validation
We can autowire MessageSource to UserController.java which is configured in CustomMessageSourceConfiguration.java to get messages.properties file.
#Autowired
private MessageSource messageSource;
public User createUser(#Valid #ModelAttribute("user") #RequestBody User
user) {
final ArrayList errorList = new ArrayList<>() {};
bindingResult.getFieldErrors().forEach(fieldError -> {
errorList.add(new
ObjectError(fieldError.getField(),messageSource.getMessage(fieldError.getCode(),
null, Locale.getDefault())));
});
Now we get required error message mapping from messages.properties file.
Way to Validate
We can create seperate bean validation.
Users.java
#Entity
#Table(name = "users")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#InBetweenNumberCustom(min = 12,max = 18)
private Integer age;
//getters and setters
}
Here we are going to create #InBetweenNumberCustom validation annotation.
import com.something.validator.ConstraintValidator.InBetweenNumberValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(InBetweenNumberCustom.List.class)
#Documented
#Constraint(validatedBy = {InBetweenNumberValidator.class})
public #interface InBetweenNumberCustom {
String message() default "Must be in between {min} and {max}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default 2147483647;
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface List {
InBetweenNumberCustom[] value();
}
}
InBetweenNumberValidator.java
import com.something.validator.annonations.InBetweenNumberCustom;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class InBetweenNumberValidator implements
ConstraintValidator<InBetweenNumberCustom,Integer> {
private int minValue;
private int maxValue;
#Override
public void initialize(InBetweenNumberCustom inBetweenNumberCustom) {
this.minValue = inBetweenNumberCustom.min();
this.maxValue = inBetweenNumberCustom.max();
}
#Override
public boolean isValid(Integer aInteger, ConstraintValidatorContext
constraintValidatorContext) {
// null values are not valid
if ( aInteger == null ) return false;
else return aInteger <= this.maxValue && aInteger >= this.minValue;
}
}
UserController.java
public JSONObject createUser(#Validated #RequestBody User user,
BindingResult bindingResult) {
...
...
}

Is there any way that we can use two custom error messages using spring-boot custom validation?

I'm using the below custom validation code to validate personName and it seems to be working fine, but the problem is when am passing an EMPTY string, it is giving same error message instead of the empty error message. Can someone please help me with this?
#Documented
#Constraint(validatedBy = {PersonNameValidator.class})
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#ReportAsSingleViolation
public #interface PersonName {
/**
* Default error message defined for the validator.
*
* #return message
*/
String message() default "invalid person name";
/**
* Method to define groups parameters for validation.
*
* #return groups
*/
Class[] groups() default {};
/**
* Method to load payload.
*
* #return payload
*/
Class<? extends Payload>[] payload() default {};
}
public class PersonNameValidator implements ConstraintValidator<PersonName, String> {
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
if (name.length() == 0) {
throw new IllegalArgumentException("must not be Empty");
} else if (!name.matches("(?=^(?!\\s*$).+)(^[^±!#£$%^&*_+§€#¢§¶•«\\\\/<>?:;|=]{1,256}$)")) {
throw new IllegalArgumentException("name should start with uppercase.");
}
return true;
}
}
#Data
public class NameDto {
#NotNull
#PersonName
private String family1Name;
#PersonName
private String family2Name;
#NotNull
#PersonName
private String givenName;
#PersonName
private String middleName;
}
Getting NullPointerException
#Name
#NotEmpty(message = "name cannot be empty")
String name;
should work
but if you want to join constraint you should use a custom ConstraintValidator add provide this validator via #Constraint(validatedBy = {YourCustomValidator.class}
see example below
full example
used dependency spring-boot-starter-validation (not needed if you use spring-boot-starter-web)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
application.properties
upper.name=dirk
application
package stackoverflow.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#SpringBootApplication
public class SoCustomValidationApplication {
public static void main(String[] args) {
SpringApplication.run(SoCustomValidationApplication.class, args);
}
}
#Component
class ConfigurationLoader{
final MyCustomValidatedProperties config;
ConfigurationLoader(MyCustomValidatedProperties config){
this.config = config;
}
#EventListener()
void showName() {
System.err.println("name is: " + config.getName());
}
}
#org.springframework.context.annotation.Configuration
#Validated
#ConfigurationProperties(prefix = "upper")
class MyCustomValidatedProperties {
#Uppercase
#NotEmpty(message = "name cannot be empty")
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Constraint(validatedBy = {ValidNameValidator.class})
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#ReportAsSingleViolation
#interface Uppercase {
String message() default "name should start with uppercase";
Class[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class ValidNameValidator implements ConstraintValidator<Uppercase, String> {
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
if (null == name || 0 == name.length() ) {
throw new IllegalArgumentException("name cannot be empty.");
} else if(!name.matches("^([A-Z][a-z]+)")) {
throw new IllegalArgumentException("name should start with uppercase.");
}
return true;
}
}
APPLICATION FAILED TO START
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'upper' to stackoverflow.demo.MyCustomValidatedProperties$$EnhancerBySpringCGLIB$$d0094cdb failed:
Property: upper.name
Value: dirk
Origin: class path resource [application.properties]:1:12
Reason: name should start with uppercase
and if you leave upper.name empty
upper.name=
APPLICATION FAILED TO START
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'upper' to stackoverflow.demo.MyCustomValidatedProperties$$EnhancerBySpringCGLIB$$29925f50 failed:
Property: upper.name
Value:
Origin: class path resource [application.properties]:1:12
Reason: name cannot be empty
Property: upper.name
Value:
Origin: class path resource [application.properties]:1:12
Reason: name should start with uppercase

Resources