Skip to main content

HTTP Part Validation

HTTP parts can be automatically validated against their schema definitions using format validation and Jakarta Bean Validation constraints.

Format Validation

Juneau supports comprehensive format validation for HTTP parts based on JSON Schema Draft 2020-12 and OpenAPI 3.x specifications. When a format is specified in a @Schema annotation, the value is automatically validated against that format.

Supported Formats

The following format types are supported:

Email Formats

  • email - Email address (RFC 5321)
  • idn-email - Internationalized email address (RFC 6531)

Hostname Formats

  • hostname - Internet host name (RFC 1123)
  • idn-hostname - Internationalized host name (RFC 5890)

IP Address Formats

  • ipv4 - IPv4 address (RFC 2673)
  • ipv6 - IPv6 address (RFC 4291)

URI/IRI Formats

  • uri - Universal Resource Identifier (RFC 3986)
  • uri-reference - URI Reference (RFC 3986)
  • iri - Internationalized Resource Identifier (RFC 3987)
  • iri-reference - IRI Reference (RFC 3987)

Other Formats

  • uuid - Universally Unique Identifier (RFC 4122)
  • uri-template - URI Template (RFC 6570)
  • json-pointer - JSON Pointer (RFC 6901)
  • relative-json-pointer - Relative JSON Pointer
  • regex - Regular expression (ECMA-262)
  • date - Full date (RFC 3339)
  • date-time - Date and time (RFC 3339)
  • date-time-zone - Date and time with time zone (RFC 3339)
  • time - Time (RFC 3339)
  • duration - Duration (RFC 3339 Appendix A / ISO 8601)

Transformation Formats

  • byte - BASE-64 encoded characters
  • binary - Hexadecimal encoded octets
  • binary-spaced - Space-separated hexadecimal octets
  • password - Password (UI hint only, no validation)

Format Validation Examples

Example - Email Validation
@RestPost("/users")
public User createUser(
@Query(name="email", schema=@Schema(format="email"))
String email
) {
// Email is automatically validated as a valid email address
// Invalid emails will throw SchemaValidationException
}
Example - UUID Validation
@RestGet("/users/{id}")
public User getUser(
@Path(name="id", schema=@Schema(format="uuid"))
String id
) {
// ID is validated as a valid UUID format
// e.g., "550e8400-e29b-41d4-a716-446655440000"
}
Example - URI Validation
@RestPost("/links")
public void addLink(
@FormData(name="url", schema=@Schema(format="uri", required=true))
String url
) {
// URL is validated as a valid URI
// Must include scheme (e.g., "https://example.com")
}
Example - Date/Time Validation
@RestGet("/appointments")
public List<Appointment> getAppointments(
@Query(name="startDate", schema=@Schema(format="date"))
String startDate,

@Query(name="endDate", schema=@Schema(format="date"))
String endDate
) {
// Dates are validated as RFC 3339 full-date (YYYY-MM-DD)
}

Validation Behavior

  • Validation Points: Format validation occurs in both validateInput() (for incoming string values) and validateOutput() (for serialized objects)
  • Error Handling: Validation failures throw SchemaValidationException with detailed error messages
  • Null Handling: The literal string "null" is treated as a valid value for all formats
  • Relaxed Patterns: Date and time formats use relaxed patterns to accommodate various serialization formats
  • Transformation Formats: Formats like byte, binary, and password are treated as transformation hints rather than validation constraints

Jakarta Bean Validation Integration

Juneau automatically detects and processes Jakarta Bean Validation constraints without requiring a direct dependency on jakarta.validation-api. This allows you to use standard validation annotations alongside Juneau's @Schema annotation.

Supported Constraints

The following Jakarta Bean Validation constraints are automatically mapped to OpenAPI schema properties:

ConstraintMapping
@NotNullrequired=true
@Size(min=X, max=Y)minLength=X, maxLength=Y, minItems=X, maxItems=Y
@Min(value)minimum=value
@Max(value)maximum=value
@Pattern(regexp)pattern=regexp
@Emailformat="email"
@Positiveminimum=0, exclusiveMinimum=true
@PositiveOrZerominimum=0
@Negativemaximum=0, exclusiveMaximum=true
@NegativeOrZeromaximum=0
@NotEmptyrequired=true, minLength=1, minItems=1
@NotBlankrequired=true, minLength=1, pattern=".*\\S.*"
@DecimalMin(value, inclusive)minimum=value with optional exclusiveMinimum
@DecimalMax(value, inclusive)maximum=value with optional exclusiveMaximum

Jakarta Validation Examples

Example - Basic Validation
import jakarta.validation.constraints.*;

public class CreateUserRequest {
@NotNull
@Email
@Size(max=255)
private String email;

@NotBlank
@Size(min=8, max=100)
@Pattern(regexp="^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).+$")
private String password;

@Positive
@Max(150)
private Integer age;
}

@RestPost("/users")
public User createUser(@Content CreateUserRequest request) {
// Jakarta Validation constraints are automatically applied
// Invalid requests throw SchemaValidationException
}
Example - Decimal Constraints
import jakarta.validation.constraints.*;
import java.math.BigDecimal;

public class ProductRequest {
@NotBlank
@Size(min=1, max=200)
private String name;

@NotNull
@DecimalMin(value="0.01", inclusive=true)
@DecimalMax(value="999999.99", inclusive=true)
private BigDecimal price;

@PositiveOrZero
private Integer stock;
}
Example - Combining Juneau and Jakarta Annotations
public class SearchRequest {
@NotBlank
@Pattern(regexp="^[a-zA-Z0-9 ]+$")
@Size(min=3, max=100)
@Schema(description="Search query", example="Juneau framework")
private String query;

@PositiveOrZero
@Max(1000)
@Schema(description="Maximum results", _default="10")
private Integer limit;

@NotNull
@Pattern(regexp="^(asc|desc)$")
@Schema(description="Sort order", _enum={"asc", "desc"})
private String order;
}

How It Works

  1. Automatic Detection: Juneau uses reflection to detect annotations in the jakarta.validation.constraints package
  2. No Direct Dependency: The main Juneau modules do not depend on jakarta.validation-api, making it optional
  3. Schema Mapping: Jakarta Validation constraints are automatically translated to OpenAPI schema properties
  4. Validation Integration: Constraints are applied during HTTP part parsing and validation
  5. OpenAPI Documentation: Constraints appear in generated OpenAPI/Swagger documentation

Validation on REST Parameters

Jakarta Validation annotations can be applied directly to REST method parameters:

Example - Parameter-Level Validation
@RestPost("/transfer")
public Response transfer(
@FormData(name="amount")
@NotNull
@DecimalMin("0.01")
@DecimalMax("10000.00")
BigDecimal amount,

@FormData(name="fromAccount")
@NotBlank
@Pattern(regexp="^[0-9]{10}$")
String fromAccount,

@FormData(name="toAccount")
@NotBlank
@Pattern(regexp="^[0-9]{10}$")
String toAccount
) {
// Validation is automatically applied before method execution
}

Validation with Remote Proxies

Jakarta Validation annotations also work seamlessly with REST client remote proxies:

Example - Remote Interface with Validation
@Remote(path="/api/users")
public interface UserService {

@RemotePost("/create")
User createUser(
@FormData(name="email")
@NotNull
@Email
String email,

@FormData(name="username")
@NotBlank
@Size(min=3, max=50)
@Pattern(regexp="^[a-zA-Z0-9_]+$")
String username,

@FormData(name="age")
@Positive
@Max(150)
Integer age
);
}

// Usage - validation occurs automatically
RestClient client = RestClient.create().build();
UserService service = client.getRemote(UserService.class);

// This will throw SchemaValidationException if email is invalid
service.createUser("invalid-email", "user123", 25);

Best Practices

Combining Validation Approaches

You can combine format validation, Jakarta Validation, and custom @Schema constraints for comprehensive validation:

Example - Comprehensive Validation
@RestPost("/products")
public Product createProduct(
@Content
@NotNull
Product product
) {...}

public class Product {
@NotBlank
@Size(min=1, max=200)
@Schema(description="Product name", example="Coffee Maker")
private String name;

@NotNull
@DecimalMin("0.01")
@DecimalMax("999999.99")
@Schema(description="Product price in USD", format="decimal")
private BigDecimal price;

@Email
@Schema(description="Contact email", format="email")
private String contactEmail;

@Pattern(regexp="^https?://.*")
@Schema(description="Product website", format="uri")
private String website;

@Size(max=36)
@Pattern(regexp="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
@Schema(description="Product SKU", format="uuid")
private String sku;
}

Error Handling

Validation errors throw SchemaValidationException which can be caught and handled:

Example - Custom Error Handling
@RestPost("/users")
public Response createUser(@Content CreateUserRequest request) {
try {
// Validation happens automatically during parameter parsing
User user = userService.create(request);
return Response.ok(user);
} catch (SchemaValidationException e) {
return Response
.status(400)
.entity(new ErrorResponse("Validation failed: " + e.getMessage()))
.build();
}
}

Testing with Validation

When writing tests, validation can be bypassed or verified:

Example - Testing Validation
@Test
public void testEmailValidation() {
RestClient client = MockRestClient
.create(MyResource.class)
.build();

// This should fail validation
assertThrows(SchemaValidationException.class, () -> {
client.post("/users")
.query("email", "invalid-email")
.complete();
});

// This should pass validation
client.post("/users")
.query("email", "valid@example.com")
.complete()
.assertStatus(200);
}

See Also

Discussion

Share feedback or follow-up questions for this page directly through GitHub.