help with custom validator

hey guys. can smb help me out with writing my won validation annotation? i have entityPayment and DTO PaymentDTO and they have private BigDecimal amount field. i want to validate it bc i want this field's length to be 7 positions at maximum. so far i have this:
@Documented
@Constraint(validatedBy = AmountValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AmountConstraint {
String message() default "Invalid amount";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Documented
@Constraint(validatedBy = AmountValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AmountConstraint {
String message() default "Invalid amount";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class AmountValidator implements ConstraintValidator<AmountConstraint, ???> {
@Override
public void initialize(AmountConstraint amountConstraint){}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext constraintValidatorContext) {
boolean result= amountField!=null&& fields max length is less than 7 positions
return result;
}
}
public class AmountValidator implements ConstraintValidator<AmountConstraint, ???> {
@Override
public void initialize(AmountConstraint amountConstraint){}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext constraintValidatorContext) {
boolean result= amountField!=null&& fields max length is less than 7 positions
return result;
}
}
1. what to write instead of ??? in my AmountValidator? should it be data type of my field? i.e. BigDecimal? 2. how to check if my field's max length is less than 7 positions in isValid method in the AmountValidator? thanks in advance.
18 Replies
JavaBot
JavaBot6mo ago
This post has been reserved for your question.
Hey @bambyzas! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
Tomasm21
Tomasm216mo ago
Hi there, 1. It should be the same type of an object or Java primitive types of the value for which you do logics and comparing in the isValid(BigDecimal amountField, ConstraintValidatorContext ctx medthod. In your case it's BigDecimal 2. Convert BigDecimal to String and check the length of string so it would be no bigger than seven. You can also use regex. Spoon-feeding:
@Component
public class AmountValidator implements ConstraintValidator<AmountConstraint, BigDecimal> {

@Override
public void initialize(AmountConstraint amountConstraint) {}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext ctx) {
if(amountField != null) {
String plainString = amountField.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}
return false;
}
}
@Component
public class AmountValidator implements ConstraintValidator<AmountConstraint, BigDecimal> {

@Override
public void initialize(AmountConstraint amountConstraint) {}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext ctx) {
if(amountField != null) {
String plainString = amountField.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}
return false;
}
}
Test:
@SpringBootTest(classes = SpaceShelfMain.class)
public class BigDecimalRegexTest {

@Autowired
AmountValidator amountValidator;

public static boolean hasUpTo7Digits(BigDecimal value) {
String plainString = value.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}

@Test
@Order(1)
public void bigDecimalTest() {
BigDecimal value1 = new BigDecimal("1234567");
BigDecimal value2 = new BigDecimal("123.4567");
BigDecimal value3 = new BigDecimal("12345678");
BigDecimal value4 = new BigDecimal("1234.5678");

assertTrue(hasUpTo7Digits(value1));
assertTrue(hasUpTo7Digits(value2));
assertFalse(hasUpTo7Digits(value3));
assertFalse(hasUpTo7Digits(value4));
}

@Test
@Order(2)
public void amountValidatorTest() {
BigDecimal value1 = new BigDecimal("1234567");
BigDecimal value2 = new BigDecimal("123.4567");
BigDecimal value3 = new BigDecimal("12345678");
BigDecimal value4 = new BigDecimal("1234.5678");

ConstraintValidatorContext ctx = Mockito.mock(ConstraintValidatorContext.class);

assertTrue(amountValidator.isValid(value1, ctx));
assertTrue(amountValidator.isValid(value2, ctx));
assertFalse(amountValidator.isValid(value3, ctx));
assertFalse(amountValidator.isValid(value4, ctx));
value1 = null;
assertFalse(amountValidator.isValid(value1, ctx));
}
}
@SpringBootTest(classes = SpaceShelfMain.class)
public class BigDecimalRegexTest {

@Autowired
AmountValidator amountValidator;

public static boolean hasUpTo7Digits(BigDecimal value) {
String plainString = value.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}

@Test
@Order(1)
public void bigDecimalTest() {
BigDecimal value1 = new BigDecimal("1234567");
BigDecimal value2 = new BigDecimal("123.4567");
BigDecimal value3 = new BigDecimal("12345678");
BigDecimal value4 = new BigDecimal("1234.5678");

assertTrue(hasUpTo7Digits(value1));
assertTrue(hasUpTo7Digits(value2));
assertFalse(hasUpTo7Digits(value3));
assertFalse(hasUpTo7Digits(value4));
}

@Test
@Order(2)
public void amountValidatorTest() {
BigDecimal value1 = new BigDecimal("1234567");
BigDecimal value2 = new BigDecimal("123.4567");
BigDecimal value3 = new BigDecimal("12345678");
BigDecimal value4 = new BigDecimal("1234.5678");

ConstraintValidatorContext ctx = Mockito.mock(ConstraintValidatorContext.class);

assertTrue(amountValidator.isValid(value1, ctx));
assertTrue(amountValidator.isValid(value2, ctx));
assertFalse(amountValidator.isValid(value3, ctx));
assertFalse(amountValidator.isValid(value4, ctx));
value1 = null;
assertFalse(amountValidator.isValid(value1, ctx));
}
}
Tomasm21
Tomasm216mo ago
Results:
No description
bambyzas
bambyzasOP6mo ago
thanks. but i just found out that i can use annotations: https://www.baeldung.com/javax-bigdecimal-validation
Baeldung
Javax BigDecimal Validation | Baeldung
A quick and practical overview of BigDecimal validation using Javax.
JavaBot
JavaBot6mo ago
If you are finished with your post, please close it. If you are not, please ignore this message. Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.
bambyzas
bambyzasOP6mo ago
would it make more sense rather than writing my own?
Tomasm21
Tomasm216mo ago
As you wish. Just test that those annotations really work on BigDecimal. I'll test with @Digits(integer=7, fraction=6). This allows 13 length number and you need 7 digits. If you choose @Digits(integer=7, fraction=0) then you have 7 digits integer part but you will not be able to enter fractional part. Your 99999.99 would throw an exception. I would choose the AmountValidator. You give then any freedom to a BigDecimal to have any amount of fractional part and overall length of 7. And these @Digts(.. can set only exact amount for integer and fractional parts.
bambyzas
bambyzasOP6mo ago
but thats what i need
Tomasm21
Tomasm216mo ago
I have tested with @Digits(integer=7, fraction=0):
@Entity
@Table(name = "registered_user")
@NoArgsConstructor
@Getter
@Setter
public class User {
//....
@Digits(integer=7, fraction = 0)
private BigDecimal travelledOnFoot;
//....
}
@Entity
@Table(name = "registered_user")
@NoArgsConstructor
@Getter
@Setter
public class User {
//....
@Digits(integer=7, fraction = 0)
private BigDecimal travelledOnFoot;
//....
}
And made a test:
@SpringBootTest(classes = SpaceShelfMain.class)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shelfspace",
"spring.datasource.username=Tomasm21",
"spring.datasource.password=Tomasm21-xi",
"spring.jpa.hibernate.ddl-auto=update",
"spring.jpa.show-sql=true",
"spring.jpa.properties.hibernate.show_sql=true"
})
public class BigDecimalRegexTest {

@Autowired
AmountValidator amountValidator;

@Autowired
private UserRepository userRepository;
//......

@Test
@Order(3)
public void userBigDecimalFieldTest() {
User user = userRepository.findByEmail("[email protected]");

user.setTravelledOnFoot(new BigDecimal("1234567"));
User savedUser = userRepository.save(user);

BigDecimal userTravelled = savedUser.getTravelledOnFoot();
assertEquals(new BigDecimal("1234567"), userTravelled);

User user2 = userRepository.findByEmail("[email protected]");

user2.setTravelledOnFoot(new BigDecimal("123.4567"));
User savedUser2 = userRepository.save(user2);

BigDecimal userTravelled2 = savedUser2.getTravelledOnFoot();
assertEquals(new BigDecimal("123.4567"), userTravelled2);
}
}
@SpringBootTest(classes = SpaceShelfMain.class)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shelfspace",
"spring.datasource.username=Tomasm21",
"spring.datasource.password=Tomasm21-xi",
"spring.jpa.hibernate.ddl-auto=update",
"spring.jpa.show-sql=true",
"spring.jpa.properties.hibernate.show_sql=true"
})
public class BigDecimalRegexTest {

@Autowired
AmountValidator amountValidator;

@Autowired
private UserRepository userRepository;
//......

@Test
@Order(3)
public void userBigDecimalFieldTest() {
User user = userRepository.findByEmail("[email protected]");

user.setTravelledOnFoot(new BigDecimal("1234567"));
User savedUser = userRepository.save(user);

BigDecimal userTravelled = savedUser.getTravelledOnFoot();
assertEquals(new BigDecimal("1234567"), userTravelled);

User user2 = userRepository.findByEmail("[email protected]");

user2.setTravelledOnFoot(new BigDecimal("123.4567"));
User savedUser2 = userRepository.save(user2);

BigDecimal userTravelled2 = savedUser2.getTravelledOnFoot();
assertEquals(new BigDecimal("123.4567"), userTravelled2);
}
}
And got an exception:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:571)
//...
Caused by: jakarta.persistence.RollbackException: Error while committing the transaction
//...
Caused by: jakarta.validation.ConstraintViolationException: Validation failed for classes [com.ShelfSpace.ShelfSpace.model.User] during update time for groups [jakarta.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='numeric value out of bounds (<7 digits>.<0 digits> expected)', propertyPath=travelledOnFoot, rootBeanClass=class com.ShelfSpace.ShelfSpace.model.User, messageTemplate='{jakarta.validation.constraints.Digits.message}'}
]
//...
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:571)
//...
Caused by: jakarta.persistence.RollbackException: Error while committing the transaction
//...
Caused by: jakarta.validation.ConstraintViolationException: Validation failed for classes [com.ShelfSpace.ShelfSpace.model.User] during update time for groups [jakarta.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='numeric value out of bounds (<7 digits>.<0 digits> expected)', propertyPath=travelledOnFoot, rootBeanClass=class com.ShelfSpace.ShelfSpace.model.User, messageTemplate='{jakarta.validation.constraints.Digits.message}'}
]
//...
With those @Digits(integer=7, fraction = 0) I can enter a number like one million and take all 7 digits. But we cannot enter a decimal number with fractional part. So that's it. I choose AmountValidator. You can use BigDecimal and use @Digits only if you know how big the number can be and how many digits does the fractional part takes.
bambyzas
bambyzasOP6mo ago
thats bc u didnt specify fraction fractional part will always be two digits in my cases
Tomasm21
Tomasm216mo ago
if you would specify 2, like @Digits(integer=7, fraction = 2) the overall positions is 7+2 = 9 You said you need 7 positions at maximum If you use @Digits then your integer part is at maximum 5 then @Digits(integer=5, fraction = 2) - now you will have 7 positions. But you integer number cannot exceed 99999.01
bambyzas
bambyzasOP6mo ago
yep,. thats correct
Tomasm21
Tomasm216mo ago
And this:
@Component
public class AmountValidator implements ConstraintValidator<AmountConstraint, BigDecimal> {

@Override
public void initialize(AmountConstraint amountConstraint) {}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext ctx) {
if(amountField != null) {
String plainString = amountField.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}
return false;
}
}
@Component
public class AmountValidator implements ConstraintValidator<AmountConstraint, BigDecimal> {

@Override
public void initialize(AmountConstraint amountConstraint) {}

@Override
public boolean isValid(BigDecimal amountField, ConstraintValidatorContext ctx) {
if(amountField != null) {
String plainString = amountField.toPlainString();
plainString = plainString.replace(".", "");
return plainString.length() <= 7;
}
return false;
}
}
will give you the possibility to enter such BigDecimals like 0.123456 to 9999999 using all 7 positions for digits no matter integer part and fractional part
tjoener
tjoener6mo ago
99999.99 to be exact
Tomasm21
Tomasm216mo ago
Yea
tjoener
tjoener6mo ago
I like the 5.2 approach tbh If that's all he needs
Tomasm21
Tomasm216mo ago
Use as you need it. You've just said that you need only 2 positions for fractional part. Then you will have 5 positions for integers at max and will have 7 positions overall. @Digits(integer=5, fraction = 2) will serve you well for it.
JavaBot
JavaBot6mo ago
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived. If your question was not answered yet, feel free to re-open this post or create a new one. In case your post is not getting any attention, you can try to use /help ping. Warning: abusing this will result in moderative actions taken against you.

Did you find this page helpful?