A comprehensive Java implementation of the JSON Schema validation specification supporting both draft v4 and v3 with complete validation capabilities and extensibility.
—
Built-in format attribute validation for common data formats and extensible format validation system. Format validation provides semantic validation beyond basic JSON Schema types, ensuring data conforms to specific formats like email addresses, URIs, dates, and custom formats.
Base interface for implementing format validation logic.
/**
* Interface for implementing custom format validation
*/
public interface FormatAttribute {
/**
* Get supported JSON node types for this format
* @return EnumSet of NodeType values this format can validate
*/
EnumSet<NodeType> supportedTypes();
/**
* Validate format against instance data
* @param report ProcessingReport for collecting validation results
* @param bundle MessageBundle for error messages
* @param data FullData containing schema and instance information
* @throws ProcessingException if format validation processing fails
*/
void validate(ProcessingReport report, MessageBundle bundle, FullData data) throws ProcessingException;
}
/**
* Abstract base class providing common functionality for format attributes
*/
public abstract class AbstractFormatAttribute implements FormatAttribute {
/**
* Constructor with format name and supported types
* @param fmt Format name (e.g., "email", "date-time")
* @param first Required supported type
* @param other Additional supported types
*/
protected AbstractFormatAttribute(String fmt, NodeType first, NodeType... other);
/**
* Get format name
* @return String format name
*/
protected final String getFormatName();
/**
* Get supported node types
* @return EnumSet of supported NodeType values
*/
public final EnumSet<NodeType> supportedTypes();
/**
* Create new processing message for this format
* @param data Validation data
* @param bundle Message bundle
* @param key Message key
* @return ProcessingMessage for error reporting
*/
protected final ProcessingMessage newMsg(FullData data, MessageBundle bundle, String key);
}/**
* Email format validation (RFC 5322)
*/
public final class EmailAttribute extends AbstractFormatAttribute {
public EmailAttribute();
}
/**
* URI format validation (RFC 3986)
*/
public final class URIAttribute extends AbstractFormatAttribute {
public URIAttribute();
}
/**
* Date-time format validation (RFC 3339)
*/
public final class DateTimeAttribute extends AbstractFormatAttribute {
public DateTimeAttribute();
}
/**
* Hostname format validation (RFC 1034)
*/
public final class HostnameAttribute extends AbstractFormatAttribute {
public HostnameAttribute();
}
/**
* IPv4 address format validation
*/
public final class IPv4Attribute extends AbstractFormatAttribute {
public IPv4Attribute();
}
/**
* IPv6 address format validation (RFC 4291)
*/
public final class IPv6Attribute extends AbstractFormatAttribute {
public IPv6Attribute();
}
/**
* Regular expression format validation
*/
public final class RegexAttribute extends AbstractFormatAttribute {
public RegexAttribute();
}/**
* Date format validation (YYYY-MM-DD)
*/
public final class DateAttribute extends AbstractFormatAttribute {
public DateAttribute();
}
/**
* Time format validation (HH:MM:SS)
*/
public final class TimeAttribute extends AbstractFormatAttribute {
public TimeAttribute();
}
/**
* Phone number format validation
*/
public final class PhoneAttribute extends AbstractFormatAttribute {
public PhoneAttribute();
}
/**
* UTC milliseconds timestamp format validation
*/
public final class UTCMillisecAttribute extends AbstractFormatAttribute {
public UTCMillisecAttribute();
}/**
* Base64 encoded string format validation
*/
public final class Base64Attribute extends AbstractFormatAttribute {
public Base64Attribute();
}
/**
* Hexadecimal string format validation
*/
public final class HexStringAttribute extends AbstractFormatAttribute {
public HexStringAttribute();
}
/**
* UUID format validation (RFC 4122)
*/
public final class UUIDAttribute extends AbstractFormatAttribute {
public UUIDAttribute();
}
/**
* MD5 hash format validation
*/
public final class MD5Attribute extends AbstractFormatAttribute {
public MD5Attribute();
}
/**
* SHA1 hash format validation
*/
public final class SHA1Attribute extends AbstractFormatAttribute {
public SHA1Attribute();
}
/**
* SHA256 hash format validation
*/
public final class SHA256Attribute extends AbstractFormatAttribute {
public SHA256Attribute();
}
/**
* SHA512 hash format validation
*/
public final class SHA512Attribute extends AbstractFormatAttribute {
public SHA512Attribute();
}Pre-built collections of format attributes for different JSON Schema versions.
/**
* Common format attributes dictionary
*/
public final class CommonFormatAttributesDictionary {
/**
* Get dictionary of common format attributes
* @return Dictionary containing email, uri, datetime, hostname, ipv4, ipv6, regex formats
*/
public static Dictionary<FormatAttribute> get();
}
/**
* Draft v3 specific format attributes dictionary
*/
public final class DraftV3FormatAttributesDictionary {
/**
* Get dictionary of Draft v3 format attributes
* @return Dictionary containing date, time, phone, utc-millisec formats
*/
public static Dictionary<FormatAttribute> get();
}
/**
* Draft v4 format attributes dictionary
*/
public final class DraftV4FormatAttributesDictionary {
/**
* Get dictionary of Draft v4 format attributes
* @return Dictionary containing common formats for Draft v4
*/
public static Dictionary<FormatAttribute> get();
}
/**
* Extra format attributes dictionary
*/
public final class ExtraFormatsDictionary {
/**
* Get dictionary of additional format attributes
* @return Dictionary containing base64, hex, uuid, md5, sha1, sha256, sha512 formats
*/
public static Dictionary<FormatAttribute> get();
}import com.github.fge.jsonschema.cfg.ValidationConfiguration;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// Enable format validation (disabled by default for performance)
ValidationConfiguration config = ValidationConfiguration.newBuilder()
.setUseFormat(true)
.freeze();
JsonSchemaFactory factory = JsonSchemaFactory.newBuilder()
.setValidationConfiguration(config)
.freeze();
// Schema with format constraints
ObjectMapper mapper = new ObjectMapper();
JsonNode schema = mapper.readTree("{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"email\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"email\"\n" +
" },\n" +
" \"website\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"uri\"\n" +
" },\n" +
" \"birthdate\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"date-time\"\n" +
" },\n" +
" \"server\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"hostname\"\n" +
" }\n" +
" }\n" +
"}");
JsonSchema jsonSchema = factory.getJsonSchema(schema);
// Valid instance
JsonNode validInstance = mapper.readTree("{\n" +
" \"email\": \"user@example.com\",\n" +
" \"website\": \"https://example.com\",\n" +
" \"birthdate\": \"1990-01-15T08:30:00Z\",\n" +
" \"server\": \"api.example.com\"\n" +
"}");
ProcessingReport validReport = jsonSchema.validate(validInstance);
System.out.println("Valid: " + validReport.isSuccess()); // true
// Invalid instance with format errors
JsonNode invalidInstance = mapper.readTree("{\n" +
" \"email\": \"invalid-email\",\n" +
" \"website\": \"not-a-uri\",\n" +
" \"birthdate\": \"invalid-date\",\n" +
" \"server\": \"invalid_hostname\"\n" +
"}");
ProcessingReport invalidReport = jsonSchema.validate(invalidInstance);
if (!invalidReport.isSuccess()) {
System.out.println("Format validation errors:");
for (ProcessingMessage message : invalidReport) {
System.out.println(" " + message.getMessage());
}
}import com.github.fge.jsonschema.format.AbstractFormatAttribute;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.processors.data.FullData;
/**
* Custom format attribute for validating ISBN numbers
*/
public final class ISBNFormatAttribute extends AbstractFormatAttribute {
private static final String FORMAT_NAME = "isbn";
public ISBNFormatAttribute() {
super(FORMAT_NAME, NodeType.STRING);
}
@Override
public void validate(ProcessingReport report, MessageBundle bundle,
FullData data) throws ProcessingException {
JsonNode instance = data.getInstance().getNode();
String isbn = instance.asText();
if (!isValidISBN(isbn)) {
ProcessingMessage message = newMsg(data, bundle, "err.format.isbn")
.putArgument("value", isbn);
report.error(message);
}
}
private boolean isValidISBN(String isbn) {
// Remove hyphens and spaces
String cleanISBN = isbn.replaceAll("[\\s-]", "");
// Check ISBN-10 or ISBN-13
return isValidISBN10(cleanISBN) || isValidISBN13(cleanISBN);
}
private boolean isValidISBN10(String isbn) {
if (isbn.length() != 10) return false;
int sum = 0;
for (int i = 0; i < 9; i++) {
if (!Character.isDigit(isbn.charAt(i))) return false;
sum += (isbn.charAt(i) - '0') * (10 - i);
}
char checkChar = isbn.charAt(9);
int checkDigit = (checkChar == 'X') ? 10 : Character.getNumericValue(checkChar);
return (sum + checkDigit) % 11 == 0;
}
private boolean isValidISBN13(String isbn) {
if (isbn.length() != 13) return false;
int sum = 0;
for (int i = 0; i < 12; i++) {
if (!Character.isDigit(isbn.charAt(i))) return false;
int digit = isbn.charAt(i) - '0';
sum += (i % 2 == 0) ? digit : digit * 3;
}
int checkDigit = Character.getNumericValue(isbn.charAt(12));
return (sum + checkDigit) % 10 == 0;
}
}
// Register custom format attribute
Library customLibrary = DraftV4Library.get().thaw()
.addFormatAttribute("isbn", new ISBNFormatAttribute())
.freeze();
ValidationConfiguration config = ValidationConfiguration.newBuilder()
.addLibrary("http://json-schema.org/draft-04/schema#", customLibrary)
.setUseFormat(true)
.freeze();
JsonSchemaFactory factory = JsonSchemaFactory.newBuilder()
.setValidationConfiguration(config)
.freeze();
// Use custom format in schema
String schemaWithISBN = "{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"book_isbn\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"isbn\"\n" +
" }\n" +
" }\n" +
"}";/**
* Service for format validation with multiple format libraries
*/
public class FormatValidationService {
private final JsonSchemaFactory standardFactory;
private final JsonSchemaFactory extendedFactory;
public FormatValidationService() {
// Standard factory with built-in formats
ValidationConfiguration standardConfig = ValidationConfiguration.newBuilder()
.setUseFormat(true)
.freeze();
this.standardFactory = JsonSchemaFactory.newBuilder()
.setValidationConfiguration(standardConfig)
.freeze();
// Extended factory with additional formats
Library extendedLibrary = DraftV4Library.get().thaw()
.addFormatAttribute("isbn", new ISBNFormatAttribute())
.addFormatAttribute("credit-card", new CreditCardFormatAttribute())
.addFormatAttribute("iban", new IBANFormatAttribute())
.freeze();
ValidationConfiguration extendedConfig = ValidationConfiguration.newBuilder()
.addLibrary("http://json-schema.org/draft-04/schema#", extendedLibrary)
.setUseFormat(true)
.freeze();
this.extendedFactory = JsonSchemaFactory.newBuilder()
.setValidationConfiguration(extendedConfig)
.freeze();
}
public ProcessingReport validateWithStandardFormats(JsonNode schema, JsonNode instance) throws ProcessingException {
JsonSchema jsonSchema = standardFactory.getJsonSchema(schema);
return jsonSchema.validate(instance);
}
public ProcessingReport validateWithExtendedFormats(JsonNode schema, JsonNode instance) throws ProcessingException {
JsonSchema jsonSchema = extendedFactory.getJsonSchema(schema);
return jsonSchema.validate(instance);
}
public List<String> getSupportedFormats(boolean includeExtended) {
List<String> formats = Arrays.asList(
"email", "uri", "date-time", "hostname", "ipv4", "ipv6", "regex"
);
if (includeExtended) {
List<String> extended = new ArrayList<>(formats);
extended.addAll(Arrays.asList("isbn", "credit-card", "iban", "base64", "uuid"));
return extended;
}
return formats;
}
}/**
* Format validator with conditional validation logic
*/
public final class ConditionalEmailAttribute extends AbstractFormatAttribute {
public ConditionalEmailAttribute() {
super("conditional-email", NodeType.STRING);
}
@Override
public void validate(ProcessingReport report, MessageBundle bundle,
FullData data) throws ProcessingException {
JsonNode instance = data.getInstance().getNode();
JsonNode schema = data.getSchema().getNode();
String email = instance.asText();
// Check if strict validation is enabled in schema
boolean strictMode = schema.path("strictEmail").asBoolean(false);
if (strictMode) {
validateStrictEmail(email, report, bundle, data);
} else {
validateBasicEmail(email, report, bundle, data);
}
}
private void validateStrictEmail(String email, ProcessingReport report,
MessageBundle bundle, FullData data) throws ProcessingException {
// Strict RFC 5322 validation
if (!email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")) {
ProcessingMessage message = newMsg(data, bundle, "err.format.email.strict")
.putArgument("value", email);
report.error(message);
}
}
private void validateBasicEmail(String email, ProcessingReport report,
MessageBundle bundle, FullData data) throws ProcessingException {
// Basic email validation
if (!email.contains("@") || !email.contains(".")) {
ProcessingMessage message = newMsg(data, bundle, "err.format.email.basic")
.putArgument("value", email);
report.error(message);
}
}
}
// Usage in schema
String conditionalSchema = "{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"email\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"conditional-email\",\n" +
" \"strictEmail\": true\n" +
" }\n" +
" }\n" +
"}";email - Email addresses (RFC 5322)uri - Uniform Resource Identifiers (RFC 3986)date-time - Date and time (RFC 3339)hostname - Internet hostnames (RFC 1034)ipv4 - IPv4 addressesipv6 - IPv6 addresses (RFC 4291)regex - Regular expressionsdate - Date in YYYY-MM-DD formattime - Time in HH:MM:SS formatphone - Phone number validationutc-millisec - UTC milliseconds timestampbase64 - Base64 encoded stringshex - Hexadecimal stringsuuid - UUID strings (RFC 4122)md5 - MD5 hash stringssha1 - SHA1 hash stringssha256 - SHA256 hash stringssha512 - SHA512 hash stringssetUseFormat(true))Install with Tessl CLI
npx tessl i tessl/maven-com-github-fge--json-schema-validator