DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Spring Boot: Cross-Origin AJAX HTTP Requests
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • How To Build Web Service Using Spring Boot 2.x

Trending

  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  • Monolith: The Good, The Bad and The Ugly
  • Scaling Microservices With Docker and Kubernetes on Production
  • How to Create a Successful API Ecosystem
  1. DZone
  2. Coding
  3. Frameworks
  4. Custom Annotations To Validate Input Requests in Spring Boot - Part I

Custom Annotations To Validate Input Requests in Spring Boot - Part I

In this article, we will discuss how we can implement custom annotations on input request for conditionally mandatory fields in Spring Boot.

By 
Trinadh Chakravarthi user avatar
Trinadh Chakravarthi
·
Aug. 21, 21 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
7.8K Views

Join the DZone community and get the full member experience.

Join For Free

In our day-to-day programming, we would have used many Spring Boot default annotations that can be used for validation like @NotNull, @Size, @NotBlank, @Digits, and many more, which is a cool way to validate any incoming request.

Consider a scenario, there are some fields that are optional by default and it has to be mandatory if some other field is populated by a specific value. 

Spring doesn't have predefined annotation for this kind of validation.

Let us take some examples and see how we can simplify the validation process, make it reusable code, and bring abstraction at annotation level. 

In a typical sale platform, there will be sale operations and void sale operations.  The amount would be mandatory in a sale operation, and reversal type would be mandatory in case of a void sale operation. 

Our dto classes are as follows:

public class IncomingRequestDto {
    
    public TransactionType transactionType;
    public ReversalType reversalType;
    public String reversalId;
    public AmountDto amountDto;

}

IncomingRequestDto has several properties like transactionType, reversalType as ENUMS.

public enum TransactionType {
    SALE {
        public String toString() {
            return "Sale";
        }
    },
    VOIDSALE {
        public String toString() {
            return "VoidSale";
        }
    },
}
public enum ReversalType {
    TIMEDOUT {
        public final String toString() {
            return "Timedout";
        }
    },
    CANCELLED {
        public final String toString() {
            return "Cancelled";
        }
    }
}

And amountDto as:

public class AmountDto {
    
    public String value;

}

Scenario 1: amountDto.value is conditional.  When we received a request that has transactionType="SALE", amountDto.value should be mandatory.

Scenario 2: reversalType is conditional.  When we received a request that has transactionType="VOIDSALE", reversalType should be mandatory. 

Let us define an annotation first with required properties for validation process:

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
public @interface NotNullIfAnotherFieldHasValue {
    String fieldName();
    String fieldValue();
    String dependFieldName();
    String message();
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotNullIfAnotherFieldHasValue[] value();
    }
}

fieldName and fieldValue will be defined on which we have to search for a specific value.  Here it is "Sale."

dependFieldName will be defined on which we have to search for a value. 

Lets us implement the above interface now:

public class NotNullIfAnotherFieldHasValueValidator
        implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
    private String fieldName;
    private String expectedFieldValue;
    private String dependFieldName;

    @Override
    public void initialize(NotNullIfAnotherFieldHasValue annotation) {
        fieldName = annotation.fieldName();
        expectedFieldValue = annotation.fieldValue();
        dependFieldName = annotation.dependFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext ctx) {
        String fieldValue = "";
        String dependFieldValue = "";
        if (value == null) {
            return true;
        }
        try {
            fieldValue = BeanUtils.getProperty(value, fieldName);
            dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
            return validate(fieldValue, dependFieldValue, ctx);

        } catch (NestedNullException ex) {
            dependFieldValue = StringUtils.isNotBlank(dependFieldValue) ? dependFieldValue : "";
            try {
                return validate(fieldValue, dependFieldValue, ctx);
            } catch (NumberFormatException exception) {
                return false;
            }
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NumberFormatException | NullPointerException ex) {
            return false;
        }
    }

    private boolean validate(String fieldValue,
                             String dependFieldValue, ConstraintValidatorContext ctx) {
        if (!StringUtils.isBlank(fieldValue)) {
             if (expectedFieldValue.equals(fieldValue) && (StringUtils.isBlank(dependFieldValue))) {
                ctx.disableDefaultConstraintViolation();
                ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
                        .addNode(dependFieldName)
                        .addConstraintViolation();
                return false;
            }
        } else {
            return false;
        }
        return true;
    }
}

Here we need to go back and decorate our interface with its implementation class as below:

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
@Documented
public @interface NotNullIfAnotherFieldHasValue {

That's it!  We are finished with implementation!  Let us decorate our IncomingRequestDto class with our custom annotation:

@JsonDeserialize(as = IncomingRequestDto.class)
@NotNullIfAnotherFieldHasValue.List({

        @NotNullIfAnotherFieldHasValue(
                fieldName = "transactionType",
                fieldValue = "Sale",
                dependFieldName = "amountDto.value",
                message = " - amount is mandatory for Sale requests"),
        
})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IncomingRequestDto {

    public TransactionType transactionType;
    public ReversalType reversalType;
    public String reversalId;
    public AmountDto amountDto;

}

By adding the annotation as above, requests will be rejected as BAD with HTTP 400 where amountDto.value is not populated for Sale type requests.  We can add as many as validations we want inside the List without changing any code as follows:

@JsonDeserialize(as = IncomingRequestDto.class)
@NotNullIfAnotherFieldHasValue.List({

        @NotNullIfAnotherFieldHasValue(
                fieldName = "transactionType",
                fieldValue = "Sale",
                dependFieldName = "amountDto.value",
                message = " - amount is mandatory for Sale requests"),

        @NotNullIfAnotherFieldHasValue(
                fieldName = "transactionType",
                fieldValue = "VoidSale",
                dependFieldName = "reversalType",
                message = " - Reversal Type is mandatory for VoidSale requests"),


})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IncomingRequestDto {

    public TransactionType transactionType;
    public ReversalType reversalType;
    public String reversalId;
    public AmountDto amountDto;

}

Refer to the GitHub page for complete implementation.

Similarly, in another scenario, there are some fields that are optional by default and it has to be mandatory if the other two fields are populated by specific value (two field validation). We will discuss this in part 2.

Cheers!

Spring Framework Annotation Spring Boot Requests

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot: Cross-Origin AJAX HTTP Requests
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • How To Build Web Service Using Spring Boot 2.x

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!