Implementing Custom Validation Annotations in Spring Boot

Spring Boot's validation framwork provides server-side data validation capabilities, though this approach increases server load due to the extensive code executino path required for HTTP requests. This makes Spring particularly suitable for systems with moderate real-time requirements and sufficient resoucres.

Custom Validator Implementation

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PositiveMessageValidator implements ConstraintValidator<PositiveMessageCheck, String> {

    @Override
    public boolean isValid(String inputValue, ConstraintValidatorContext validatorContext) {
        if(inputValue == null || inputValue.trim().isEmpty()) {
            return true;
        }
        String processedMessage = inputValue.trim();
        return !processedMessage.contains("negative phrase") && !processedMessage.contains("unmotivated");
    }
}

Custom Annotation Definition

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PositiveMessageValidator.class)
@Documented
public @interface PositiveMessageCheck {
    String message() default "Message must be positive";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Data Model with Validation Constraints

import javax.validation.constraints.NotNull;
import javax.validation.constraints.DecimalMax;
import java.math.BigDecimal;
import java.util.Date;

public class ProductSale {
    private Integer productId;
    
    @NotNull(message = "Product name is required")
    private String productName;
    
    @DecimalMax(message = "Quantity cannot exceed 10000", value = "10000")
    private BigDecimal quantity;
    
    private BigDecimal unitPrice;
    private BigDecimal totalValue;
    
    @PositiveMessageCheck(message = "Please provide motivational comments")
    private String remarks;
    
    private Date transactionDate;

    // Getter and setter methods
    public Integer getProductId() { return productId; }
    public void setProductId(Integer productId) { this.productId = productId; }
    
    public String getProductName() { return productName; }
    public void setProductName(String productName) { this.productName = productName; }
    
    public BigDecimal getQuantity() { return quantity; }
    public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
    
    public BigDecimal getUnitPrice() { return unitPrice; }
    public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
    
    public BigDecimal getTotalValue() { return totalValue; }
    public void setTotalValue(BigDecimal totalValue) { this.totalValue = totalValue; }
    
    public String getRemarks() { return remarks; }
    public void setRemarks(String remarks) { this.remarks = remarks; }
    
    public Date getTransactionDate() { return transactionDate; }
    public void setTransactionDate(Date transactionDate) { this.transactionDate = transactionDate; }
}

Controller Implementation

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@Controller
@RequestMapping("/api/sales")
public class SalesController {
    
    @Autowired
    private SalesProcessingService salesService;
    
    @PostMapping("/record")
    @ResponseBody
    public Object createSalesRecord(@RequestBody @Valid ProductSale saleData, Errors validationErrors) {
        if (validationErrors.hasErrors()) {
            return ValidationUtil.extractErrorMessages(validationErrors);
        }
        
        try {
            if (saleData.getTotalValue() == null) {
                saleData.setTotalValue(saleData.getQuantity().multiply(saleData.getUnitPrice()));
            }
            return salesService.processSale(saleData);
        } catch (DataValidationException ex) {
            return ApiResponse.error(ex.getMessage());
        }
    }
}

Client-Side Test Code

const requestConfig = {
    url: 'http://localhost:9999/api/sales/record',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    data: JSON.stringify({
        productName: "Rice",
        quantity: 10000.1,
        unitPrice: 1000,
        remarks: "negative phrase"
    })
};

$.ajax(requestConfig).done(function(response) {
    console.log(response);
});

Validation Response

{
    "remarks": "Please provide motivational comments",
    "quantity": "Quantity cannot exceed 10000"
}

Tags: Spring Boot Validation Custom Annotations java Data Validation

Posted on Fri, 08 May 2026 17:59:12 +0000 by danielson2k