A small Java 21 library that compiles XSD 1.0 with Xerces and returns safe, structured validation reports. It keeps raw XML values out of issues while adding indexed paths, QNames, expected elements, grouped Xerces keys, identity constraint names, and honest coverage state.
The current development line requires Java 21 or newer. The latest published 0.1.x release supports Java 17 or newer and is available from Maven Central.
Maven:
<dependency>
<groupId>io.github.khopland.xsd.validation</groupId>
<artifactId>xsd-validator</artifactId>
<version>0.1.1</version>
</dependency>Gradle:
implementation("io.github.khopland.xsd.validation:xsd-validator:0.1.1")BetterXsdValidator validator =
BetterXsdValidator.compile(new StreamSource(schemaFile));
ValidationReport report =
validator.validate(new StreamSource(xmlFile));
for (ValidationIssue issue : report.issues()) {
System.out.println(issue.path() + ": " + issue.message());
}Compile once and reuse the validator, including across threads. Each call to
validate creates an isolated Xerces session.
Hard processing limits can be changed without recompiling the schema:
validator = validator.withLimits(new ValidationLimits(512, 200));The two-argument constructor changes structural limits while retaining the default budgets of 1,000,000 total elements and 64 Mi UTF-16 code units from character data and attribute values. All four limits can be set explicitly:
validator = validator.withLimits(new ValidationLimits(
512,
200,
2_000_000,
128L * 1024 * 1024));Higher limits allow more work and structural state while processing one document. These budgets do not replace an application-owned byte limit or read deadline on untrusted input streams.
Supply untrusted XML through a byte or character stream whose total size and
read time are bounded by the application. A StreamSource or SAXSource that
contains only a system ID is restricted to a local file; validation does not
dereference a remote primary document. To validate deliberately approved remote
content, open it at the application trust boundary and pass the resulting
bounded stream. A system ID may still accompany supplied content as origin or
base-URI metadata.
By default, schema imports, includes, and redefines are restricted to local
files. Set a system ID on the schema Source when it uses relative
dependencies. For classpath, JAR, in-memory, or deliberately approved remote
dependencies, call compile(schemaSource, resolver) with a standard
LSResourceResolver. The resolver is an explicit trust boundary; resolved
dependencies are fingerprinted and still cannot contain a DOCTYPE.
Schema compilation is also bounded by default: 16 MiB for the root schema, 64 dependencies, 16 MiB per dependency, and 64 MiB across dependencies. Trusted schema sets that need higher limits can opt in explicitly:
SchemaCompilationLimits limits =
new SchemaCompilationLimits(
32 * 1024 * 1024,
128,
32 * 1024 * 1024,
128L * 1024 * 1024);
BetterXsdValidator validator =
BetterXsdValidator.compile(schemaSource, resolver, limits);For a fully trusted schema bundle whose size is controlled outside the library, the library caps can be deliberately removed at the call site:
BetterXsdValidator validator = BetterXsdValidator.compile(
schemaSource,
resolver,
SchemaCompilationLimits.TRUSTED_UNBOUNDED);TRUSTED_UNBOUNDED must not be used for user-supplied schemas or an untrusted
resolver. JVM memory and maximum array sizes still apply.
validis true only when parsing completed and Xerces reported no errors.completeis false after a fatal XML or processing error.rawEventCountcounts Xerces events before related events are grouped.issuesare immutable, ordered, capped at 100, and contain no raw lexical XML values. Namespaced path segments use{namespace}local[index].actualElement,actualAttribute, andconstraintNameare nullable.SchemaIdentityaccepts a nullable target namespace and normalizes it to an empty string; the rest of the API is non-null by default through JSpecify.schemacontains the target namespace and a dependency-aware SHA-256 fingerprint.coveragereports incomplete parsing, truncation, and allowed wildcard content that was skipped or only laxly assessed.- Default processing stops with
XML_PROCESSING_ERRORbeyond 256 nested elements, 100 distinct child QNames under one parent, 1,000,000 total elements, or 64 Mi UTF-16 code units in character data and attribute values. UsewithLimits(ValidationLimits)to change those bounds.
Use ValidationIssue.code() for application logic. Messages are safe prose,
not a parsing interface.
Choice-specific codes are conservative enrichments of Xerces content errors.
They are produced for non-repeating choice particles when the selected branch
can be established from retained sibling context. Choices inside repeating
particles, or cases that depend on children older than the latest 100 retained
under one parent, fall back to the general element and content issue codes. The
100-child history is a fixed diagnostic-retention cap;
withLimits(ValidationLimits) changes distinct-child tracking, not this cap.
Given the submitted XML <age>customer-secret</age> and an xs:int
declaration, the report contains safe structural information:
code: INVALID_VALUE
path: /age[1]
message: Element <age> does not satisfy type 'int'.
schemaCodes: [cvc-datatype-valid.1.2.1, cvc-type.3.1.3]
The submitted value does not appear in the issue, report toString(), or
grouped schema codes.
This library validates XML against XSD 1.0. Business rules, code-list rules, Schematron assertions, and requirements not expressed by the compiled XSD are outside its validation result.
Run the complete test, packaging, dependency-analysis, and coverage-reporting build with:
mvn clean verifyThe JaCoCo report is written to target/site/jacoco. Public binary and source
compatibility with the latest released API baseline is checked separately:
mvn -Papi-compatibility clean verify| Family | Codes |
|---|---|
| XML and root | MALFORMED_XML, XML_PROCESSING_ERROR, ROOT_NAMESPACE_MISMATCH, UNDECLARED_ROOT |
| Elements and content | UNEXPECTED_ELEMENT, MISSING_ELEMENT, DUPLICATE_ELEMENT, MIN_OCCURS_NOT_MET, MAX_OCCURS_EXCEEDED |
| Choices | CHOICE_ALREADY_SELECTED, CHOICE_BRANCH_INCOMPLETE |
| Values and facets | INVALID_VALUE, ENUMERATION_VIOLATION, PATTERN_MISMATCH, LENGTH_VIOLATION, MINIMUM_VIOLATION, MAXIMUM_VIOLATION, TOTAL_DIGITS_EXCEEDED, FRACTION_DIGITS_EXCEEDED |
| Attributes | REQUIRED_ATTRIBUTE_MISSING, ATTRIBUTE_NOT_ALLOWED, INVALID_ATTRIBUTE_VALUE, ATTRIBUTE_FIXED_VALUE_MISMATCH |
| Identity constraints | DUPLICATE_KEY, DUPLICATE_UNIQUE, KEY_REFERENCE_NOT_FOUND, KEY_VALUE_MISSING |
| Substitution and instance hints | ABSTRACT_ELEMENT_REQUIRES_SUBSTITUTE, INVALID_XSI_TYPE, XSI_TYPE_NOT_FOUND, XSI_TYPE_NOT_DERIVED, XSI_NIL_NOT_ALLOWED, NILLED_ELEMENT_HAS_CONTENT, XSI_NIL_FIXED_VALUE_CONFLICT |
| Fallback | SCHEMA_VALIDATION_ERROR |
Licensed under the Apache License 2.0.