Java SDK Reference¶
Installation¶
Maven¶
<dependency>
<groupId>com.anyvali</groupId>
<artifactId>anyvali</artifactId>
<version>0.0.1</version>
</dependency>
Gradle¶
implementation 'com.anyvali:anyvali:0.0.1'
Quick Start¶
import static com.anyvali.AnyVali.*;
import com.anyvali.*;
import java.util.Map;
import java.util.Set;
// Define a schema
ObjectSchema userSchema = object_(Map.of(
"name", string().minLength(1).maxLength(100),
"email", string().format("email"),
"age", int_().min(0).max(150)
));
// Parse input (throws on failure)
Map<String, Object> user = userSchema.parse(Map.of(
"name", "Alice",
"email", "alice@example.com",
"age", 30
));
// Safe parse (returns result object)
ParseResult<Map<String, Object>> result = userSchema.safeParse(Map.of(
"name", "",
"email", "bad"
));
if (!result.success()) {
for (ValidationIssue issue : result.issues()) {
System.out.printf("%s: [%s] %s%n",
issue.path(), issue.code(), issue.message());
}
}
Schema Builders¶
All schemas are created through static methods on the AnyVali class. Use import static com.anyvali.AnyVali.* for concise builder syntax.
Primitive Types¶
import static com.anyvali.AnyVali.*;
var str = string();
var num = number(); // float64
var integer = int_(); // int64
var flag = bool_();
var nil = null_();
Note: int_(), bool_(), null_(), and any_() use trailing underscores to avoid conflicts with Java reserved words.
Numeric Types¶
// Floating point
var f32 = float32();
var f64 = float64();
var number = number(); // alias for float64
// Signed integers
var i = int_(); // int64 (default)
var i8 = int8(); // -128 to 127
var i16 = int16(); // -32,768 to 32,767
var i32 = int32(); // -2^31 to 2^31-1
var i64 = int64(); // -2^63 to 2^63-1
// Unsigned integers
var u8 = uint8(); // 0 to 255
var u16 = uint16(); // 0 to 65,535
var u32 = uint32(); // 0 to 4,294,967,295
var u64 = uint64(); // 0 to int64 max (capped for safety)
Special Types¶
var any = any_(); // accepts any value
var unk = unknown(); // accepts any value (stricter intent)
var nev = never(); // always fails
Literal and Enum¶
import java.util.List;
// Literal: must be exactly this value
var status = literal("active");
// Enum: must be one of these values
var role = enum_(List.of("admin", "user", "guest"));
Collections¶
// Array of strings
var tags = array(string());
// Tuple: fixed-length, each position typed
var point = tuple_(List.of(number(), number()));
// Record: string keys, all values share a schema
var scores = record(int_());
Object¶
// All properties required by default
var user = object_(Map.of(
"name", string().minLength(1),
"email", string().format("email"),
"age", int_().min(0)
));
// Specify which fields are required
var flexUser = object_(
Map.of(
"name", string().minLength(1),
"email", string().format("email"),
"age", int_().min(0)
),
Set.of("name", "email") // age is optional
);
// Control unknown key handling
var looseUser = object_(
Map.of("name", string()),
Set.of("name"),
UnknownKeyMode.STRIP // silently remove unknown keys
);
The unknownKeys parameter controls how keys not declared in the properties are handled:
| Mode | Behavior |
|---|---|
REJECT (default) |
Produces an unknown_key issue for each extra key |
STRIP |
Silently removes extra keys from the output |
ALLOW |
Passes extra keys through to the output |
Composition¶
// Union: value must match at least one variant
var stringOrInt = union(List.of(string(), int_()));
// Intersection: value must match all schemas
var named = intersection(List.of(
object_(Map.of("name", string())),
object_(Map.of("age", int_()))
));
Modifiers¶
// Optional: field may be absent
var maybeAge = optional(int_());
// Nullable: field may be null
var nullableName = nullable(string());
// Chain .optional() or .nullable() on any schema
var schema = string().optional();
var schema2 = int_().nullable();
References¶
// Reference a named definition (used with export/import)
var ref = ref("User");
String Constraints¶
var schema = string()
.minLength(1) // minimum character count
.maxLength(255) // maximum character count
.pattern("^\\w+$") // regex pattern
.startsWith("hello") // must start with prefix
.endsWith(".com") // must end with suffix
.includes("@") // must contain substring
.format("email"); // built-in format validator
Each constraint method returns a new schema instance -- schemas are immutable.
Built-in Formats¶
"email"-- RFC 5322 email address"uri"-- absolute URI"uuid"-- UUID v4"iso8601"-- ISO 8601 date-time"ipv4"-- IPv4 address"ipv6"-- IPv6 address
var email = string().format("email");
var uri = string().format("uri");
var id = string().format("uuid");
Numeric Constraints¶
Constraints apply to all numeric schemas (NumberSchema, IntSchema, Float32Schema, Float64Schema).
var price = number()
.min(0) // value >= 0
.max(10000) // value <= 10000
.exclusiveMin(0) // value > 0
.exclusiveMax(10000) // value < 10000
.multipleOf(0.01); // must be a multiple of 0.01
var port = int_()
.min(1)
.max(65535);
var evenNumber = int_().multipleOf(2);
Parsing¶
parse (Throwing)¶
parse() returns the validated and typed value or throws ValidationError.
try {
String name = string().parse("Alice");
Long age = int_().parse(30);
Double price = number().parse(9.99);
Boolean active = bool_().parse(true);
Map<String, Object> user = object_(Map.of(
"name", string()
)).parse(Map.of("name", "Alice"));
} catch (ValidationError e) {
for (ValidationIssue issue : e.issues()) {
System.out.printf("[%s] %s%n", issue.code(), issue.message());
}
}
safeParse (Non-Throwing)¶
safeParse() returns a ParseResult<T> record that never throws.
ParseResult<String> result = string().safeParse(42);
if (result.success()) {
System.out.println("Parsed: " + result.data());
} else {
for (ValidationIssue issue : result.issues()) {
System.out.printf("[%s] %s%n", issue.code(), issue.message());
// [invalid_type] Expected string, received integer
}
}
ParseResult<Map<String, Object>> userResult = object_(Map.of(
"name", string(),
"age", int_()
)).safeParse(Map.of("name", "Alice", "age", 30));
if (userResult.success()) {
Map<String, Object> user = userResult.data();
System.out.println(user.get("name"));
}
Defaults and Coercion¶
Defaults¶
Defaults fill in missing (absent) values. They run after coercion and before validation. Call .withDefault(value) on any schema.
The default only applies when the value is absent -- if a value is present, it is validated normally. Defaults must be static values (for portability across SDKs).
var role = string().withDefault("user");
role.parse(null); // => "user" (absent value filled)
role.parse("admin"); // => "admin"
var count = int_().withDefault(0);
var tags = array(string()).withDefault(List.of());
Defaults work with optional fields in objects:
var config = object_(Map.of(
"theme", optional(string().withDefault("light")),
"language", optional(string().withDefault("en"))
));
config.parse(Map.of());
// => {"theme": "light", "language": "en"}
If the default value itself fails validation, a default_invalid issue is produced.
Coercion¶
Coercion transforms the input value before validation. It runs only when the value is present. Call .coerce(config) on any schema to enable coercion.
Available Coercions¶
String to Integer
var age = int_().coerce(new CoercionConfig().from("string"));
age.parse("42"); // => 42L (string coerced to integer)
age.parse(42); // => 42L (already an integer, no coercion needed)
String to Number
var price = number().coerce(new CoercionConfig().from("string"));
price.parse("3.14"); // => 3.14
String to Boolean
var flag = bool_().coerce(new CoercionConfig().from("string"));
flag.parse("true"); // => true
flag.parse("false"); // => false
flag.parse("1"); // => true
flag.parse("0"); // => false
Trim Whitespace
var trimmed = string().coerce(new CoercionConfig().trim(true));
trimmed.parse(" hello "); // => "hello"
Lowercase / Uppercase
var lower = string().coerce(new CoercionConfig().lower(true));
lower.parse("HELLO"); // => "hello"
var upper = string().coerce(new CoercionConfig().upper(true));
upper.parse("hello"); // => "HELLO"
Transformations can be combined:
var normalized = string().coerce(new CoercionConfig().trim(true).lower(true));
normalized.parse(" Hello World "); // => "hello world"
Export and Import¶
Export¶
import static com.anyvali.AnyVali.*;
import java.util.Map;
var schema = object_(Map.of(
"name", string().minLength(1),
"age", int_().min(0)
));
// Export to Map
Map<String, Object> doc = exportSchema(schema);
// Export to JSON string
String json = exportSchemaJson(schema);
System.out.println(json);
// {
// "anyvaliVersion": "1.0",
// "schemaVersion": "1",
// "root": {
// "kind": "object",
// "properties": { ... },
// ...
// },
// "definitions": {},
// "extensions": {}
// }
// Export via schema instance
Map<String, Object> doc2 = schema.export();
Import¶
// Import from a document map or JSON string
Schema<?> imported = importSchema(doc);
// Use it like any other schema
var result = imported.safeParse(Map.of("name", "Bob", "age", 25));
Cross-Language Example¶
// Java side: define and export
var schema = object_(Map.of(
"id", int64(),
"email", string().format("email")
));
String json = exportSchemaJson(schema);
// Send JSON to another service using Python, Go, C#, etc.
Type Inference¶
The Java SDK uses Schema<T> generics to carry output types through the parse pipeline:
| Schema Class | Output Type (T) |
|---|---|
StringSchema |
String |
NumberSchema |
Double |
IntSchema |
Long |
BoolSchema |
Boolean |
ObjectSchema |
Map<String, Object> |
ArraySchema |
List<Object> |
When you call parse(), the return type is fully typed:
String name = string().parse("Alice"); // returns String
Long age = int_().parse(30); // returns Long
Double price = number().parse(9.99); // returns Double
Boolean active = bool_().parse(true); // returns Boolean
Map<String, Object> obj = object_(Map.of(
"x", int_()
)).parse(Map.of("x", 1)); // returns Map<String, Object>
Validation Issues¶
When parsing fails, structured ValidationIssue records describe each problem.
var schema = object_(Map.of(
"name", string().minLength(1),
"age", int_().min(0).max(150)
));
var result = schema.safeParse(Map.of("name", "", "age", 200));
for (ValidationIssue issue : result.issues()) {
System.out.println("Path: " + issue.path());
System.out.println("Code: " + issue.code());
System.out.println("Message: " + issue.message());
System.out.println("Expected: " + issue.expected());
System.out.println("Received: " + issue.received());
System.out.println();
}
// Path: [name]
// Code: too_small
// Message: String must have at least 1 character(s)
// Expected: 1
// Received: 0
//
// Path: [age]
// Code: too_large
// Message: Number must be <= 150.0
// Expected: 150.0
// Received: 200.0
Issue Codes¶
| Code | Meaning |
|---|---|
invalid_type |
Value is the wrong type |
too_small |
Value is below minimum |
too_large |
Value is above maximum |
invalid_string |
String constraint violated (pattern, format, etc.) |
invalid_number |
Numeric constraint violated (multipleOf, etc.) |
required |
Required object property is missing |
unknown_key |
Object has an unrecognized key |
coercion_failed |
Coercion could not convert the value |
default_invalid |
Default value failed schema validation |
Common Patterns¶
Validating Environment Variables¶
Use UnknownKeyMode.STRIP when parsing maps that contain many extra keys you don't care about, like environment variables:
var envSchema = AnyVali.object_(
Map.of("DATABASE_URL", AnyVali.string()),
Set.of("DATABASE_URL"),
UnknownKeyMode.STRIP
);
Without STRIP, parse would fail with unknown_key issues for every other variable in the environment (PATH, HOME, etc.) because the default mode is REJECT.
| Mode | What happens with extra keys |
|---|---|
REJECT (default) |
Parse fails with unknown_key issues |
STRIP |
Extra keys silently removed from output |
ALLOW |
Extra keys passed through to output |
Eagerly Evaluated vs Lazy Defaults¶
.withDefault() accepts any value of the correct type. Expressions like System.getProperty("user.dir") are evaluated immediately when the schema is created and stored as a static value -- this works fine. What AnyVali does not support is lazy supplier defaults that re-evaluate on each parse call. If you need a fresh value on every parse, apply it after:
var configSchema = object_(
Map.of(
"profile", string().withDefault("default"),
"appDir", optional(string())
),
Set.of("profile"),
UnknownKeyMode.STRIP
);
Map<String, Object> config = configSchema.parse(data);
config.putIfAbsent("appDir", System.getProperty("user.dir"));
This keeps the schema fully portable -- the same JSON document can be imported in Go, Python, or any other SDK without relying on language-specific function calls.
API Reference¶
AnyVali (Static Factory)¶
| Method | Returns | Description |
|---|---|---|
string() |
StringSchema |
String schema |
number() |
NumberSchema |
Float64 number schema |
float32() |
Float32Schema |
Float32 schema |
float64() |
Float64Schema |
Float64 schema |
int_() |
IntSchema |
Int64 integer schema |
int8() |
IntSchema.Int8Schema |
Signed 8-bit integer |
int16() |
IntSchema.Int16Schema |
Signed 16-bit integer |
int32() |
IntSchema.Int32Schema |
Signed 32-bit integer |
int64() |
IntSchema.Int64Schema |
Signed 64-bit integer |
uint8() |
IntSchema.Uint8Schema |
Unsigned 8-bit integer |
uint16() |
IntSchema.Uint16Schema |
Unsigned 16-bit integer |
uint32() |
IntSchema.Uint32Schema |
Unsigned 32-bit integer |
uint64() |
IntSchema.Uint64Schema |
Unsigned 64-bit integer |
bool_() |
BoolSchema |
Boolean schema |
null_() |
NullSchema |
Null schema |
any_() |
AnySchema |
Accepts any value |
unknown() |
UnknownSchema |
Accepts any value (stricter intent) |
never() |
NeverSchema |
Always fails |
literal(value) |
LiteralSchema |
Exact value match |
enum_(values) |
EnumSchema |
One of the listed values |
array(items) |
ArraySchema |
Array with item schema |
tuple_(items) |
TupleSchema |
Fixed-length typed list |
object_(properties) |
ObjectSchema |
Object (all keys required) |
object_(properties, required) |
ObjectSchema |
Object with explicit required set |
object_(properties, required, unknownKeys) |
ObjectSchema |
Object with unknown key mode |
record(valueSchema) |
RecordSchema |
String-keyed record |
union(schemas) |
UnionSchema |
At least one schema must match |
intersection(schemas) |
IntersectionSchema |
All schemas must match |
optional(schema) |
OptionalSchema |
Value may be absent |
nullable(schema) |
NullableSchema |
Value may be null |
ref(reference) |
RefSchema |
Reference to a named definition |
exportSchema(schema) |
Map<String, Object> |
Export to document map |
exportSchemaJson(schema) |
String |
Export to JSON string |
importSchema(source) |
Schema<?> |
Import document to live schema |
Schema<T> (Base Class)¶
| Member | Signature | Description |
|---|---|---|
parse |
T parse(Object input) |
Parse or throw ValidationError |
safeParse |
ParseResult<T> safeParse(Object input) |
Parse without throwing |
optional |
OptionalSchema optional() |
Wrap as optional |
nullable |
NullableSchema nullable() |
Wrap as nullable |
withDefault |
Schema<T> withDefault(Object value) |
Set a default value |
coerce |
Schema<T> coerce(CoercionConfig config) |
Enable coercion |
export |
Map<String, Object> export() |
Export to document map |
export |
Map<String, Object> export(ExportMode mode) |
Export with explicit mode |
ParseResult<T> (Record)¶
| Accessor | Type | Description |
|---|---|---|
success() |
boolean |
Whether parsing succeeded |
data() |
T |
Parsed value (null on failure) |
issues() |
List<ValidationIssue> |
List of issues (empty on success) |
ValidationIssue (Record)¶
| Accessor | Type | Description |
|---|---|---|
code() |
String |
Issue code (e.g. "invalid_type") |
message() |
String |
Human-readable message |
path() |
List<Object> |
Path to the issue (strings and ints) |
expected() |
Object |
What was expected |
received() |
Object |
What was received |
meta() |
Map<String, Object> |
Optional metadata |
StringSchema Constraints¶
| Method | Parameter | Description |
|---|---|---|
.minLength(n) |
int |
Minimum string length |
.maxLength(n) |
int |
Maximum string length |
.pattern(p) |
String |
Regex pattern |
.startsWith(s) |
String |
Required prefix |
.endsWith(s) |
String |
Required suffix |
.includes(s) |
String |
Required substring |
.format(f) |
String |
Built-in format validator |
NumberSchema / IntSchema Constraints¶
| Method | Parameter | Description |
|---|---|---|
.min(n) |
double |
Minimum value (inclusive) |
.max(n) |
double |
Maximum value (inclusive) |
.exclusiveMin(n) |
double |
Minimum value (exclusive) |
.exclusiveMax(n) |
double |
Maximum value (exclusive) |
.multipleOf(n) |
double |
Must be a multiple of this value |
UnknownKeyMode Enum¶
| Value | Description |
|---|---|
UnknownKeyMode.REJECT |
Reject unknown keys (default) |
UnknownKeyMode.STRIP |
Remove unknown keys from output |
UnknownKeyMode.ALLOW |
Pass unknown keys through |
ExportMode Enum¶
| Value | Description |
|---|---|
ExportMode.PORTABLE |
Fails if schema contains non-portable features |
ExportMode.EXTENDED |
Emits core schema plus extension namespaces |