Java framework for developing ops-friendly, high-performance, RESTful web applications
—
Comprehensive input validation using Bean Validation (JSR-303) with custom Dropwizard validators for durations, data sizes, and other common types.
Standard JSR-303 Bean Validation annotations for validating method parameters, request bodies, and configuration objects.
// Standard validation annotations
@NotNull // Value must not be null
@NotEmpty // String/Collection must not be null or empty
@NotBlank // String must not be null, empty, or whitespace only
@Size(min = 1, max = 100) // Collection/String size constraints
@Min(1) // Numeric minimum value
@Max(100) // Numeric maximum value
@Range(min = 1, max = 100) // Numeric range
@Pattern(regexp = "\\d+") // Regular expression pattern
@Email // Valid email address format
@Valid // Cascade validation to nested objects
// Usage in JAX-RS resources
@POST
public Response createUser(@Valid @NotNull User user) {
// user is automatically validated before method execution
}
@GET
public User getUser(@PathParam("id") @NotNull @Min(1) Long id) {
// id parameter is validated
}Custom validation annotations specific to Dropwizard for common application configuration and input validation scenarios.
package io.dropwizard.validation;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DurationRangeValidator.class)
public @interface DurationRange {
/**
* Minimum duration value.
*/
long min() default 0;
/**
* Maximum duration value.
*/
long max() default Long.MAX_VALUE;
/**
* Time unit for min value.
*/
TimeUnit minUnit() default TimeUnit.MILLISECONDS;
/**
* Time unit for max value.
*/
TimeUnit maxUnit() default TimeUnit.MILLISECONDS;
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MinDurationValidator.class)
public @interface MinDuration {
/**
* Minimum duration value.
*/
long value();
/**
* Time unit for the value.
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MaxDurationValidator.class)
public @interface MaxDuration {
/**
* Maximum duration value.
*/
long value();
/**
* Time unit for the value.
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}Usage Example:
public class ServerConfiguration {
@DurationRange(min = 1, minUnit = TimeUnit.SECONDS, max = 30, maxUnit = TimeUnit.SECONDS)
private Duration connectionTimeout = Duration.seconds(5);
@MinDuration(value = 100, unit = TimeUnit.MILLISECONDS)
private Duration requestTimeout = Duration.milliseconds(500);
@MaxDuration(value = 1, unit = TimeUnit.HOURS)
private Duration sessionTimeout = Duration.minutes(30);
}Validation annotations for data size constraints with support for various units (bytes, KB, MB, GB).
package io.dropwizard.validation;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DataSizeRangeValidator.class)
public @interface DataSizeRange {
/**
* Minimum data size value.
*/
long min() default 0;
/**
* Maximum data size value.
*/
long max() default Long.MAX_VALUE;
/**
* Unit for min value.
*/
DataSize.Unit minUnit() default DataSize.Unit.BYTES;
/**
* Unit for max value.
*/
DataSize.Unit maxUnit() default DataSize.Unit.BYTES;
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MinDataSizeValidator.class)
public @interface MinDataSize {
/**
* Minimum data size value.
*/
long value();
/**
* Unit for the value.
*/
DataSize.Unit unit() default DataSize.Unit.BYTES;
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MaxDataSizeValidator.class)
public @interface MaxDataSize {
/**
* Maximum data size value.
*/
long value();
/**
* Unit for the value.
*/
DataSize.Unit unit() default DataSize.Unit.BYTES;
}Usage Example:
public class FileUploadConfiguration {
@DataSizeRange(min = 1, minUnit = DataSize.Unit.KILOBYTES,
max = 10, maxUnit = DataSize.Unit.MEGABYTES)
private DataSize maxFileSize = DataSize.megabytes(5);
@MinDataSize(value = 512, unit = DataSize.Unit.BYTES)
private DataSize bufferSize = DataSize.kilobytes(8);
@MaxDataSize(value = 100, unit = DataSize.Unit.MEGABYTES)
private DataSize totalUploadLimit = DataSize.megabytes(50);
}Validation for restricting values to a specific set of allowed options.
package io.dropwizard.validation;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = OneOfValidator.class)
public @interface OneOf {
/**
* Array of allowed values.
*/
String[] value();
/**
* Whether comparison should be case insensitive.
*/
boolean ignoreCase() default false;
/**
* Whether whitespace should be ignored.
*/
boolean ignoreWhitespace() default false;
}Usage Example:
public class ApplicationConfiguration {
@OneOf({"development", "staging", "production"})
private String environment = "development";
@OneOf(value = {"DEBUG", "INFO", "WARN", "ERROR"}, ignoreCase = true)
private String logLevel = "INFO";
@OneOf({"http", "https"})
private String protocol = "http";
}Validation for network port numbers with configurable ranges.
package io.dropwizard.validation;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PortRangeValidator.class)
public @interface PortRange {
/**
* Minimum port number.
*/
int min() default 1;
/**
* Maximum port number.
*/
int max() default 65535;
}Usage Example:
public class ServerConfiguration {
@PortRange(min = 1024, max = 65535)
private int applicationPort = 8080;
@PortRange(min = 8000, max = 9000)
private int adminPort = 8081;
}Custom validation methods for complex business logic validation that cannot be expressed with simple annotations.
package io.dropwizard.validation.selfvalidating;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SelfValidating {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidationMethod {
/**
* Error message when validation fails.
*/
String message();
/**
* Groups for conditional validation.
*/
Class<?>[] groups() default {};
}Usage Example:
@SelfValidating
public class UserRegistration {
@NotEmpty
private String username;
@NotEmpty
private String password;
@NotEmpty
private String confirmPassword;
@Email
private String email;
private int age;
@ValidationMethod(message = "Passwords do not match")
public boolean isPasswordValid() {
return password != null && password.equals(confirmPassword);
}
@ValidationMethod(message = "User must be at least 13 years old")
public boolean isAgeValid() {
return age >= 13;
}
@ValidationMethod(message = "Username cannot be the same as email")
public boolean isUsernameDistinct() {
return !username.equals(email);
}
}Conditional validation using groups to apply different validation rules in different contexts.
// Validation group interfaces
public interface CreateValidation {}
public interface UpdateValidation {}
public class User {
@NotNull(groups = {CreateValidation.class, UpdateValidation.class})
private String name;
@NotNull(groups = CreateValidation.class)
@Email(groups = {CreateValidation.class, UpdateValidation.class})
private String email;
@Null(groups = CreateValidation.class)
@NotNull(groups = UpdateValidation.class)
private Long id;
}
// Usage in resources
@POST
public User createUser(@Valid(CreateValidation.class) User user) {
// Only CreateValidation constraints are applied
}
@PUT
@Path("/{id}")
public User updateUser(@PathParam("id") Long id,
@Valid(UpdateValidation.class) User user) {
// Only UpdateValidation constraints are applied
}Creating custom validation annotations and validators for application-specific validation requirements.
// Custom validation annotation
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CreditCardValidator.class)
public @interface CreditCard {
String message() default "Invalid credit card number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
CreditCardType[] acceptedTypes() default {CreditCardType.VISA, CreditCardType.MASTERCARD};
enum CreditCardType {
VISA, MASTERCARD, AMEX, DISCOVER
}
}
// Custom validator implementation
public class CreditCardValidator implements ConstraintValidator<CreditCard, String> {
private CreditCard.CreditCardType[] acceptedTypes;
@Override
public void initialize(CreditCard constraintAnnotation) {
this.acceptedTypes = constraintAnnotation.acceptedTypes();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // Use @NotNull for null checks
}
// Remove spaces and hyphens
String cleanNumber = value.replaceAll("[\\s-]", "");
// Validate using Luhn algorithm
if (!isValidLuhn(cleanNumber)) {
return false;
}
// Check card type
CreditCard.CreditCardType type = detectCardType(cleanNumber);
return Arrays.asList(acceptedTypes).contains(type);
}
private boolean isValidLuhn(String number) {
// Luhn algorithm implementation
int sum = 0;
boolean alternate = false;
for (int i = number.length() - 1; i >= 0; i--) {
int digit = Character.getNumericValue(number.charAt(i));
if (alternate) {
digit *= 2;
if (digit > 9) {
digit = (digit % 10) + 1;
}
}
sum += digit;
alternate = !alternate;
}
return (sum % 10) == 0;
}
private CreditCard.CreditCardType detectCardType(String number) {
if (number.startsWith("4")) {
return CreditCard.CreditCardType.VISA;
} else if (number.startsWith("5")) {
return CreditCard.CreditCardType.MASTERCARD;
} else if (number.startsWith("34") || number.startsWith("37")) {
return CreditCard.CreditCardType.AMEX;
} else if (number.startsWith("6")) {
return CreditCard.CreditCardType.DISCOVER;
}
return CreditCard.CreditCardType.VISA; // Default
}
}Handling validation errors and converting them to appropriate HTTP responses with detailed error information.
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException exception) {
List<String> errors = exception.getConstraintViolations()
.stream()
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
.collect(Collectors.toList());
ErrorResponse errorResponse = new ErrorResponse("Validation failed", errors);
return Response.status(Response.Status.BAD_REQUEST)
.entity(errorResponse)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
public class ErrorResponse {
private String message;
private List<String> errors;
public ErrorResponse(String message, List<String> errors) {
this.message = message;
this.errors = errors;
}
// getters and setters
}Install with Tessl CLI
npx tessl i tessl/maven-io-dropwizard--dropwizard-project