0
# Validation
1
2
Comprehensive input validation using Bean Validation (JSR-303) with custom Dropwizard validators for durations, data sizes, and other common types.
3
4
## Capabilities
5
6
### Bean Validation
7
8
Standard JSR-303 Bean Validation annotations for validating method parameters, request bodies, and configuration objects.
9
10
```java { .api }
11
// Standard validation annotations
12
@NotNull // Value must not be null
13
@NotEmpty // String/Collection must not be null or empty
14
@NotBlank // String must not be null, empty, or whitespace only
15
@Size(min = 1, max = 100) // Collection/String size constraints
16
@Min(1) // Numeric minimum value
17
@Max(100) // Numeric maximum value
18
@Range(min = 1, max = 100) // Numeric range
19
@Pattern(regexp = "\\d+") // Regular expression pattern
20
@Email // Valid email address format
21
@Valid // Cascade validation to nested objects
22
23
// Usage in JAX-RS resources
24
@POST
25
public Response createUser(@Valid @NotNull User user) {
26
// user is automatically validated before method execution
27
}
28
29
@GET
30
public User getUser(@PathParam("id") @NotNull @Min(1) Long id) {
31
// id parameter is validated
32
}
33
```
34
35
### Dropwizard Validation Annotations
36
37
Custom validation annotations specific to Dropwizard for common application configuration and input validation scenarios.
38
39
```java { .api }
40
package io.dropwizard.validation;
41
42
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
43
@Retention(RetentionPolicy.RUNTIME)
44
@Constraint(validatedBy = DurationRangeValidator.class)
45
public @interface DurationRange {
46
/**
47
* Minimum duration value.
48
*/
49
long min() default 0;
50
51
/**
52
* Maximum duration value.
53
*/
54
long max() default Long.MAX_VALUE;
55
56
/**
57
* Time unit for min value.
58
*/
59
TimeUnit minUnit() default TimeUnit.MILLISECONDS;
60
61
/**
62
* Time unit for max value.
63
*/
64
TimeUnit maxUnit() default TimeUnit.MILLISECONDS;
65
}
66
67
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
68
@Retention(RetentionPolicy.RUNTIME)
69
@Constraint(validatedBy = MinDurationValidator.class)
70
public @interface MinDuration {
71
/**
72
* Minimum duration value.
73
*/
74
long value();
75
76
/**
77
* Time unit for the value.
78
*/
79
TimeUnit unit() default TimeUnit.MILLISECONDS;
80
}
81
82
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
83
@Retention(RetentionPolicy.RUNTIME)
84
@Constraint(validatedBy = MaxDurationValidator.class)
85
public @interface MaxDuration {
86
/**
87
* Maximum duration value.
88
*/
89
long value();
90
91
/**
92
* Time unit for the value.
93
*/
94
TimeUnit unit() default TimeUnit.MILLISECONDS;
95
}
96
```
97
98
**Usage Example:**
99
100
```java
101
public class ServerConfiguration {
102
@DurationRange(min = 1, minUnit = TimeUnit.SECONDS, max = 30, maxUnit = TimeUnit.SECONDS)
103
private Duration connectionTimeout = Duration.seconds(5);
104
105
@MinDuration(value = 100, unit = TimeUnit.MILLISECONDS)
106
private Duration requestTimeout = Duration.milliseconds(500);
107
108
@MaxDuration(value = 1, unit = TimeUnit.HOURS)
109
private Duration sessionTimeout = Duration.minutes(30);
110
}
111
```
112
113
### Data Size Validation
114
115
Validation annotations for data size constraints with support for various units (bytes, KB, MB, GB).
116
117
```java { .api }
118
package io.dropwizard.validation;
119
120
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
121
@Retention(RetentionPolicy.RUNTIME)
122
@Constraint(validatedBy = DataSizeRangeValidator.class)
123
public @interface DataSizeRange {
124
/**
125
* Minimum data size value.
126
*/
127
long min() default 0;
128
129
/**
130
* Maximum data size value.
131
*/
132
long max() default Long.MAX_VALUE;
133
134
/**
135
* Unit for min value.
136
*/
137
DataSize.Unit minUnit() default DataSize.Unit.BYTES;
138
139
/**
140
* Unit for max value.
141
*/
142
DataSize.Unit maxUnit() default DataSize.Unit.BYTES;
143
}
144
145
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
146
@Retention(RetentionPolicy.RUNTIME)
147
@Constraint(validatedBy = MinDataSizeValidator.class)
148
public @interface MinDataSize {
149
/**
150
* Minimum data size value.
151
*/
152
long value();
153
154
/**
155
* Unit for the value.
156
*/
157
DataSize.Unit unit() default DataSize.Unit.BYTES;
158
}
159
160
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
161
@Retention(RetentionPolicy.RUNTIME)
162
@Constraint(validatedBy = MaxDataSizeValidator.class)
163
public @interface MaxDataSize {
164
/**
165
* Maximum data size value.
166
*/
167
long value();
168
169
/**
170
* Unit for the value.
171
*/
172
DataSize.Unit unit() default DataSize.Unit.BYTES;
173
}
174
```
175
176
**Usage Example:**
177
178
```java
179
public class FileUploadConfiguration {
180
@DataSizeRange(min = 1, minUnit = DataSize.Unit.KILOBYTES,
181
max = 10, maxUnit = DataSize.Unit.MEGABYTES)
182
private DataSize maxFileSize = DataSize.megabytes(5);
183
184
@MinDataSize(value = 512, unit = DataSize.Unit.BYTES)
185
private DataSize bufferSize = DataSize.kilobytes(8);
186
187
@MaxDataSize(value = 100, unit = DataSize.Unit.MEGABYTES)
188
private DataSize totalUploadLimit = DataSize.megabytes(50);
189
}
190
```
191
192
### Enum and Choice Validation
193
194
Validation for restricting values to a specific set of allowed options.
195
196
```java { .api }
197
package io.dropwizard.validation;
198
199
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
200
@Retention(RetentionPolicy.RUNTIME)
201
@Constraint(validatedBy = OneOfValidator.class)
202
public @interface OneOf {
203
/**
204
* Array of allowed values.
205
*/
206
String[] value();
207
208
/**
209
* Whether comparison should be case insensitive.
210
*/
211
boolean ignoreCase() default false;
212
213
/**
214
* Whether whitespace should be ignored.
215
*/
216
boolean ignoreWhitespace() default false;
217
}
218
```
219
220
**Usage Example:**
221
222
```java
223
public class ApplicationConfiguration {
224
@OneOf({"development", "staging", "production"})
225
private String environment = "development";
226
227
@OneOf(value = {"DEBUG", "INFO", "WARN", "ERROR"}, ignoreCase = true)
228
private String logLevel = "INFO";
229
230
@OneOf({"http", "https"})
231
private String protocol = "http";
232
}
233
```
234
235
### Port Range Validation
236
237
Validation for network port numbers with configurable ranges.
238
239
```java { .api }
240
package io.dropwizard.validation;
241
242
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
243
@Retention(RetentionPolicy.RUNTIME)
244
@Constraint(validatedBy = PortRangeValidator.class)
245
public @interface PortRange {
246
/**
247
* Minimum port number.
248
*/
249
int min() default 1;
250
251
/**
252
* Maximum port number.
253
*/
254
int max() default 65535;
255
}
256
```
257
258
**Usage Example:**
259
260
```java
261
public class ServerConfiguration {
262
@PortRange(min = 1024, max = 65535)
263
private int applicationPort = 8080;
264
265
@PortRange(min = 8000, max = 9000)
266
private int adminPort = 8081;
267
}
268
```
269
270
### Self-Validating Objects
271
272
Custom validation methods for complex business logic validation that cannot be expressed with simple annotations.
273
274
```java { .api }
275
package io.dropwizard.validation.selfvalidating;
276
277
@Target({ElementType.TYPE})
278
@Retention(RetentionPolicy.RUNTIME)
279
public @interface SelfValidating {
280
}
281
282
@Target({ElementType.METHOD})
283
@Retention(RetentionPolicy.RUNTIME)
284
public @interface ValidationMethod {
285
/**
286
* Error message when validation fails.
287
*/
288
String message();
289
290
/**
291
* Groups for conditional validation.
292
*/
293
Class<?>[] groups() default {};
294
}
295
```
296
297
**Usage Example:**
298
299
```java
300
@SelfValidating
301
public class UserRegistration {
302
@NotEmpty
303
private String username;
304
305
@NotEmpty
306
private String password;
307
308
@NotEmpty
309
private String confirmPassword;
310
311
312
private String email;
313
314
private int age;
315
316
@ValidationMethod(message = "Passwords do not match")
317
public boolean isPasswordValid() {
318
return password != null && password.equals(confirmPassword);
319
}
320
321
@ValidationMethod(message = "User must be at least 13 years old")
322
public boolean isAgeValid() {
323
return age >= 13;
324
}
325
326
@ValidationMethod(message = "Username cannot be the same as email")
327
public boolean isUsernameDistinct() {
328
return !username.equals(email);
329
}
330
}
331
```
332
333
### Validation Groups
334
335
Conditional validation using groups to apply different validation rules in different contexts.
336
337
```java { .api }
338
// Validation group interfaces
339
public interface CreateValidation {}
340
public interface UpdateValidation {}
341
342
public class User {
343
@NotNull(groups = {CreateValidation.class, UpdateValidation.class})
344
private String name;
345
346
@NotNull(groups = CreateValidation.class)
347
@Email(groups = {CreateValidation.class, UpdateValidation.class})
348
private String email;
349
350
@Null(groups = CreateValidation.class)
351
@NotNull(groups = UpdateValidation.class)
352
private Long id;
353
}
354
355
// Usage in resources
356
@POST
357
public User createUser(@Valid(CreateValidation.class) User user) {
358
// Only CreateValidation constraints are applied
359
}
360
361
@PUT
362
@Path("/{id}")
363
public User updateUser(@PathParam("id") Long id,
364
@Valid(UpdateValidation.class) User user) {
365
// Only UpdateValidation constraints are applied
366
}
367
```
368
369
### Custom Validators
370
371
Creating custom validation annotations and validators for application-specific validation requirements.
372
373
```java { .api }
374
// Custom validation annotation
375
@Target({ElementType.FIELD, ElementType.PARAMETER})
376
@Retention(RetentionPolicy.RUNTIME)
377
@Constraint(validatedBy = CreditCardValidator.class)
378
public @interface CreditCard {
379
String message() default "Invalid credit card number";
380
Class<?>[] groups() default {};
381
Class<? extends Payload>[] payload() default {};
382
383
CreditCardType[] acceptedTypes() default {CreditCardType.VISA, CreditCardType.MASTERCARD};
384
385
enum CreditCardType {
386
VISA, MASTERCARD, AMEX, DISCOVER
387
}
388
}
389
390
// Custom validator implementation
391
public class CreditCardValidator implements ConstraintValidator<CreditCard, String> {
392
private CreditCard.CreditCardType[] acceptedTypes;
393
394
@Override
395
public void initialize(CreditCard constraintAnnotation) {
396
this.acceptedTypes = constraintAnnotation.acceptedTypes();
397
}
398
399
@Override
400
public boolean isValid(String value, ConstraintValidatorContext context) {
401
if (value == null) {
402
return true; // Use @NotNull for null checks
403
}
404
405
// Remove spaces and hyphens
406
String cleanNumber = value.replaceAll("[\\s-]", "");
407
408
// Validate using Luhn algorithm
409
if (!isValidLuhn(cleanNumber)) {
410
return false;
411
}
412
413
// Check card type
414
CreditCard.CreditCardType type = detectCardType(cleanNumber);
415
return Arrays.asList(acceptedTypes).contains(type);
416
}
417
418
private boolean isValidLuhn(String number) {
419
// Luhn algorithm implementation
420
int sum = 0;
421
boolean alternate = false;
422
for (int i = number.length() - 1; i >= 0; i--) {
423
int digit = Character.getNumericValue(number.charAt(i));
424
if (alternate) {
425
digit *= 2;
426
if (digit > 9) {
427
digit = (digit % 10) + 1;
428
}
429
}
430
sum += digit;
431
alternate = !alternate;
432
}
433
return (sum % 10) == 0;
434
}
435
436
private CreditCard.CreditCardType detectCardType(String number) {
437
if (number.startsWith("4")) {
438
return CreditCard.CreditCardType.VISA;
439
} else if (number.startsWith("5")) {
440
return CreditCard.CreditCardType.MASTERCARD;
441
} else if (number.startsWith("34") || number.startsWith("37")) {
442
return CreditCard.CreditCardType.AMEX;
443
} else if (number.startsWith("6")) {
444
return CreditCard.CreditCardType.DISCOVER;
445
}
446
return CreditCard.CreditCardType.VISA; // Default
447
}
448
}
449
```
450
451
## Validation Error Handling
452
453
### Validation Exception Mapping
454
455
Handling validation errors and converting them to appropriate HTTP responses with detailed error information.
456
457
```java
458
@Provider
459
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
460
@Override
461
public Response toResponse(ConstraintViolationException exception) {
462
List<String> errors = exception.getConstraintViolations()
463
.stream()
464
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
465
.collect(Collectors.toList());
466
467
ErrorResponse errorResponse = new ErrorResponse("Validation failed", errors);
468
469
return Response.status(Response.Status.BAD_REQUEST)
470
.entity(errorResponse)
471
.type(MediaType.APPLICATION_JSON)
472
.build();
473
}
474
}
475
476
public class ErrorResponse {
477
private String message;
478
private List<String> errors;
479
480
public ErrorResponse(String message, List<String> errors) {
481
this.message = message;
482
this.errors = errors;
483
}
484
485
// getters and setters
486
}
487
```