Java Refined is a Java 8+ library for refinement types, non-empty collections,
and small functional control types. The core java-refined artifact has no
runtime dependencies, and the optional java-refined-kotlin artifact adds a
single Kotlin runtime dependency on kotlin-stdlib.
It lets you move validation out of scattered if checks and into explicit
types such as PositiveInt, NonBlankString, and NonEmptyList.
This project is open source under the MIT license, so you can use, modify, and distribute it in personal or commercial codebases.
Java-only projects can use java-refined with no runtime dependencies.
Kotlin/JVM projects can optionally add java-refined-kotlin for Kotlin-first
extensions and collection adapters, which adds a single runtime dependency on
org.jetbrains.kotlin:kotlin-stdlib.
- zero runtime dependencies in the core
java-refinedmodule - optional Kotlin/JVM support module with Kotlin-first extensions and one
kotlin-stdlibruntime dependency - Java 8 bytecode baseline with Java 8+ APIs
- refined wrappers with safe
ofand throwingunsafeOfconstructors - fail-fast
Validationand error-accumulatingValidated - non-empty collection wrappers with defensive snapshots
- structured
Violationerrors with code/message/metadata - rich predicate catalog across numeric/string/collection/boolean/logical domains
- 100% test coverage (JaCoCo: instruction, branch, line, complexity, method, class)
- 95%+ mutation testing coverage (PITest)
- JSON parser with nesting depth limit (512) for DoS protection
- Why
- Terminology
- Installation
- Coordinates
- Basic Usage
- Kotlin/JVM Usage
- Error Handling
- Core Concepts
- Core API
- Supported Types
- Compatibility
- Project Status
- Contributing and Security
- License
Java applications often validate the same invariants repeatedly:
- positive numeric identifiers
- non-blank names and codes
- non-empty collections in domain logic
- fail-fast versus error-accumulating validation
Java Refined packages those invariants into reusable types and predicates so that "already validated" becomes part of the type system at the library level.
Validation means "a validation result". It contains either a valid value or an error. Violation means "a rule was broken". It carries a code, message, and optional metadata.
You do not need functional programming to use this. Treat Validation as a result
object with isValid(), get(), getError(), or fold(...).
Add the dependency coordinates shown below to your build file. The library is published to Maven Central.
Use only the core java-refined artifact when your codebase is Java-only.
The core module has no extra runtime dependencies beyond the JDK modules it already uses.
Add the optional java-refined-kotlin module only when you want Kotlin-first
extensions and collection adapters on top of the Java core API.
The Kotlin support module adds one runtime dependency: org.jetbrains.kotlin:kotlin-stdlib.
Java only:
io.github.junggikim:java-refined:1.1.0
Kotlin/JVM:
io.github.junggikim:java-refined:1.1.0
io.github.junggikim:java-refined-kotlin:1.1.0
Java only:
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.junggikim:java-refined:1.1.0")
}Kotlin/JVM:
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.junggikim:java-refined:1.1.0")
implementation("io.github.junggikim:java-refined-kotlin:1.1.0")
}Java only:
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.junggikim:java-refined:1.1.0'
}Kotlin/JVM:
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.junggikim:java-refined:1.1.0'
implementation 'io.github.junggikim:java-refined-kotlin:1.1.0'
}Java only:
<dependency>
<groupId>io.github.junggikim</groupId>
<artifactId>java-refined</artifactId>
<version>1.1.0</version>
</dependency>Kotlin/JVM:
<dependency>
<groupId>io.github.junggikim</groupId>
<artifactId>java-refined</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>io.github.junggikim</groupId>
<artifactId>java-refined-kotlin</artifactId>
<version>1.1.0</version>
</dependency>For Kotlin/JVM usage, keep access to Maven Central unless your build already provides kotlin-stdlib.
import io.github.junggikim.refined.refined.collection.NonEmptyList;
import io.github.junggikim.refined.refined.numeric.PositiveInt;
import io.github.junggikim.refined.refined.string.NonBlankString;
import io.github.junggikim.refined.validation.Validation;
import io.github.junggikim.refined.violation.Violation;
import java.util.Arrays;
Validation<Violation, PositiveInt> age = PositiveInt.of(18);
Validation<Violation, NonBlankString> name = NonBlankString.of("Ada");
Validation<Violation, NonEmptyList<String>> tags = NonEmptyList.of(Arrays.asList("java", "fp"));
Validation<Violation, String> summary =
name.zip(age, (n, a) -> n.value() + " (" + a.value() + ")");
boolean hasJavaTag = tags.get().contains("java");
Validation<Violation, NonEmptyList<String>> upperTags =
NonEmptyList.ofStream(tags.get().stream().map(String::toUpperCase));Refined wrappers are created through factory methods, not public constructors.
Use of(...) when invalid input is part of normal control flow, and use
unsafeOf(...) when invalid input should fail fast.
Collection refined types are direct-compatible immutable JDK collections.
NonEmptyList<T> can be passed anywhere a List<T> is expected, and
NonEmptyMap<K, V> can be passed anywhere a Map<K, V> is expected, without
calling an extra unwrap method.
More refined wrappers, including NaturalInt and Ipv6String:
import io.github.junggikim.refined.refined.numeric.NaturalInt;
import io.github.junggikim.refined.refined.string.Ipv6String;
Validation<Violation, NaturalInt> retries = NaturalInt.of(3);
Validation<Violation, Ipv6String> address = Ipv6String.of("2001:db8::1");
String status = address.fold(
v -> "invalid ip: " + v.message(),
ok -> "ip ok: " + ok.value()
);java-refined-kotlin is an optional Kotlin/JVM support module. It is not
required for Java-only usage.
It is a thin Kotlin-first layer on top of the core Java API. It keeps the same validation semantics while adding:
- nullable receiver extensions such as
toNonBlankString()andtoPositiveInt() Validationconvenience helpers such asgetOrNull(),errorOrNull(), andgetOrThrow()- read-only Kotlin adapters for refined non-empty collections
Sequence-based factory extensions for collection refinement
import io.github.junggikim.refined.kotlin.errorOrNull
import io.github.junggikim.refined.kotlin.getOrThrow
import io.github.junggikim.refined.kotlin.toNonBlankString
import io.github.junggikim.refined.kotlin.toNonBlankStringOrThrow
import io.github.junggikim.refined.kotlin.toNonEmptyListOrThrow
val name = "Ada".toNonBlankStringOrThrow()
val nullableName = (null as String?).toNonBlankString()
val message = nullableName.errorOrNull()!!.message
val value: String = name.value
val tags: List<String> = listOf("java", "fp").toNonEmptyListOrThrow()
val upper = tags.map(String::uppercase)
val refinedUpper = upper.toNonEmptyListOrThrow()Kotlin collection adapters are read-only views over the Java refined collections.
They keep the same validation rules and immutable snapshot semantics, but mutator
methods such as add, put, offer, or addFirst are not available from Kotlin.
This means Kotlin code can stay concise and idiomatic while Java code keeps using the same core refined wrappers and validation model.
Validation is fail-fast (stops at the first error), while Validated accumulates multiple errors.
import io.github.junggikim.refined.refined.string.NonBlankString;
import io.github.junggikim.refined.validation.Validated;
import io.github.junggikim.refined.validation.Validation;
import io.github.junggikim.refined.violation.Violation;
import java.util.Arrays;
import java.util.List;
Validation<Violation, NonBlankString> bad = NonBlankString.of(" ");
String message = bad.fold(
v -> "invalid: " + v.code() + " - " + v.message(),
ok -> "ok: " + ok.value()
);
Validated<String, Integer> left = Validated.invalid(Arrays.asList("age"));
Validated<String, Integer> right = Validated.invalid(Arrays.asList("name"));
List<String> errors = left.zip(right, Integer::sum).getErrors();Refined wrappers expose two factory methods:
of(value)returnsValidation<Violation, T>and never throws.unsafeOf(value)throwsRefinementExceptionon invalid input.
Refined wrappers do not expose public constructors, so creation always goes through of(...) or unsafeOf(...).
Use unsafeOf(...) only at trusted boundaries after validation has already happened.
Validation is fail-fast and stops at the first error. Validated accumulates multiple errors into a non-empty list.
For scalar wrappers such as PositiveInt or NonBlankString, the refined object
stores the validated runtime value and exposes it through value().
For collection wrappers such as NonEmptyList or NonEmptyMap, the refined object
is itself the collection interface. It preserves the non-empty invariant and immutable
snapshot semantics while remaining directly usable as a JDK collection.
Mutator methods such as add, remove, put, offer, or poll are not supported and throw.
of(value)- safe constructor
- returns
Validation<Violation, T>
unsafeOf(value)- validates first
- throws
RefinementExceptionon failure - use only at trusted boundaries after validation has already happened
- collection wrappers additionally expose stream-based constructors
ofStream(stream)for list/set/queue/deque/iterable/set-like wrappersofEntryStream(stream)for map-like wrappers
Violation- stable
code, human-readablemessage, immutablemetadata - collection constructors distinguish
*-empty,*-null-element,*-null-key,*-null-value, and sorted/navigable*-invalid-*failures
- stable
The library ships with refined wrappers, control types, and predicate catalogs. Each table lists a type and the invariant it enforces.
| Type | Description |
|---|---|
PositiveInt |
int > 0 |
NegativeInt |
int < 0 |
NonNegativeInt |
int >= 0 |
NonPositiveInt |
int <= 0 |
NonZeroInt |
int != 0 |
NaturalInt |
int >= 0 |
| Type | Description |
|---|---|
PositiveLong |
long > 0 |
NegativeLong |
long < 0 |
NonNegativeLong |
long >= 0 |
NonPositiveLong |
long <= 0 |
NonZeroLong |
long != 0 |
NaturalLong |
long >= 0 |
| Type | Description |
|---|---|
PositiveByte |
byte > 0 |
NegativeByte |
byte < 0 |
NonNegativeByte |
byte >= 0 |
NonPositiveByte |
byte <= 0 |
NonZeroByte |
byte != 0 |
NaturalByte |
byte >= 0 |
| Type | Description |
|---|---|
PositiveShort |
short > 0 |
NegativeShort |
short < 0 |
NonNegativeShort |
short >= 0 |
NonPositiveShort |
short <= 0 |
NonZeroShort |
short != 0 |
NaturalShort |
short >= 0 |
| Type | Description |
|---|---|
PositiveFloat |
finite float > 0 |
NegativeFloat |
finite float < 0 |
NonNegativeFloat |
finite float >= 0 |
NonPositiveFloat |
finite float <= 0 |
NonZeroFloat |
finite float != 0 |
FiniteFloat |
finite float (not NaN/Infinity) |
NonNaNFloat |
float is not NaN (Infinity allowed) |
ZeroToOneFloat |
finite float in [0, 1] |
| Type | Description |
|---|---|
PositiveDouble |
finite double > 0 |
NegativeDouble |
finite double < 0 |
NonNegativeDouble |
finite double >= 0 |
NonPositiveDouble |
finite double <= 0 |
NonZeroDouble |
finite double != 0 |
FiniteDouble |
finite double (not NaN/Infinity) |
NonNaNDouble |
double is not NaN (Infinity allowed) |
ZeroToOneDouble |
finite double in [0, 1] |
| Type | Description |
|---|---|
PositiveBigInteger |
BigInteger > 0 |
NegativeBigInteger |
BigInteger < 0 |
NonNegativeBigInteger |
BigInteger >= 0 |
NonPositiveBigInteger |
BigInteger <= 0 |
NonZeroBigInteger |
BigInteger != 0 |
NaturalBigInteger |
BigInteger >= 0 |
| Type | Description |
|---|---|
PositiveBigDecimal |
BigDecimal > 0 |
NegativeBigDecimal |
BigDecimal < 0 |
NonNegativeBigDecimal |
BigDecimal >= 0 |
NonPositiveBigDecimal |
BigDecimal <= 0 |
NonZeroBigDecimal |
BigDecimal != 0 |
| Type | Description |
|---|---|
DigitChar |
Unicode digit (Character.isDigit) |
LetterChar |
Unicode letter (Character.isLetter) |
LetterOrDigitChar |
Unicode letter or digit (Character.isLetterOrDigit) |
LowerCaseChar |
Unicode lower-case (Character.isLowerCase) |
UpperCaseChar |
Unicode upper-case (Character.isUpperCase) |
WhitespaceChar |
Unicode whitespace (Character.isWhitespace) |
SpecialChar |
not letter, digit, whitespace, or space |
| Type | Description |
|---|---|
NonEmptyString |
not empty |
NonBlankString |
not blank (Unicode whitespace) |
TrimmedString |
no leading or trailing Unicode whitespace |
LowerCaseString |
equals toLowerCase(Locale.ROOT) |
UpperCaseString |
equals toUpperCase(Locale.ROOT) |
| Type | Description |
|---|---|
AsciiString |
ASCII only (U+0000..U+007F) |
AlphabeticString |
non-empty; letters only |
NumericString |
non-empty; digits only |
AlphanumericString |
non-empty; letters or digits only |
SlugString |
lower-case slug a-z0-9 with hyphens |
| Type | Description |
|---|---|
UuidString |
valid UUID |
UlidString |
valid ULID (Crockford Base32, 26 chars) |
JwtString |
three Base64URL parts separated by dots |
SemVerString |
Semantic Versioning 2.0.0 pattern |
CreditCardString |
Luhn-valid 13-19 digits (spaces/hyphens allowed) |
IsbnString |
valid ISBN-10 or ISBN-13 (spaces/hyphens allowed) |
| Type | Description |
|---|---|
UriString |
parseable by java.net.URI |
UrlString |
parseable by java.net.URL |
| Type | Description |
|---|---|
Ipv4String |
IPv4 dotted-quad |
Ipv6String |
IPv6 address |
CidrV4String |
IPv4 CIDR with prefix /0-32 |
CidrV6String |
IPv6 CIDR with prefix /0-128 |
MacAddressString |
MAC address with :/-/. separators |
HostnameString |
hostname labels (1-63 chars, letters/digits/hyphen), total length <= 253 |
| Type | Description |
|---|---|
EmailString |
well-formed email shape (basic local@domain rules) |
RegexString |
valid regular expression |
HexString |
non-empty hex string |
HexColorString |
#RGB, #RRGGBB, or #RRGGBBAA |
Base64String |
valid Base64 (JDK decoder) |
Base64UrlString |
valid Base64 URL-safe (JDK decoder) |
JsonString |
valid JSON |
XmlString |
well-formed XML |
XPathString |
valid XPath expression |
| Type | Description |
|---|---|
Iso8601DateString |
ISO-8601 date (LocalDate.parse) |
Iso8601TimeString |
ISO-8601 time (LocalTime.parse) |
Iso8601DateTimeString |
ISO-8601 date-time (ISO_DATE_TIME) |
Iso8601DurationString |
ISO-8601 duration (Duration.parse) |
Iso8601PeriodString |
ISO-8601 period (Period.parse) |
TimeZoneIdString |
valid ZoneId |
| Type | Description |
|---|---|
ValidByteString |
parseable by Byte.parseByte |
ValidShortString |
parseable by Short.parseShort |
ValidIntString |
parseable by Integer.parseInt |
ValidLongString |
parseable by Long.parseLong |
ValidFloatString |
parseable by Float.parseFloat (NaN/Infinity allowed) |
ValidDoubleString |
parseable by Double.parseDouble (NaN/Infinity allowed) |
ValidBigIntegerString |
parseable by new BigInteger(...) |
ValidBigDecimalString |
parseable by new BigDecimal(...) |
| Type | Description |
|---|---|
NonEmptyList |
non-empty immutable List with no null elements |
NonEmptySet |
non-empty immutable Set with no null elements |
NonEmptyMap |
non-empty immutable Map with no null keys/values |
NonEmptyDeque |
non-empty immutable Deque snapshot with no null elements |
NonEmptyQueue |
non-empty immutable Queue snapshot with no null elements |
NonEmptyIterable |
non-empty immutable sequence snapshot with no null elements |
NonEmptySortedSet |
non-empty immutable SortedSet with no null elements |
NonEmptySortedMap |
non-empty immutable SortedMap with no null keys/values |
NonEmptyNavigableSet |
non-empty immutable NavigableSet with no null elements |
NonEmptyNavigableMap |
non-empty immutable NavigableMap with no null keys/values |
| Type | Description |
|---|---|
Option |
optional value (Some or None) |
Either |
left or right branch |
Try |
success or captured exception |
Ior |
inclusive-or: left, right, or both |
Validated |
error-accumulating validation result |
| Predicate | Description |
|---|---|
GreaterThan |
value > bound |
GreaterOrEqual |
value >= bound |
LessThan |
value < bound |
LessOrEqual |
value <= bound |
EqualTo |
value == bound |
NotEqualTo |
value != bound |
| Predicate | Description |
|---|---|
OpenInterval |
min < value < max |
ClosedInterval |
min <= value <= max |
OpenClosedInterval |
min < value <= max |
ClosedOpenInterval |
min <= value < max |
| Predicate | Description |
|---|---|
EvenInt |
int is even |
OddInt |
int is odd |
EvenLong |
long is even |
OddLong |
long is odd |
EvenBigInteger |
BigInteger is even |
OddBigInteger |
BigInteger is odd |
| Predicate | Description |
|---|---|
DivisibleByInt |
value % divisor == 0 |
DivisibleByLong |
value % divisor == 0 |
DivisibleByBigInteger |
value mod divisor == 0 |
NonDivisibleByInt |
value % divisor != 0 |
NonDivisibleByLong |
value % divisor != 0 |
NonDivisibleByBigInteger |
value mod divisor != 0 |
ModuloInt |
value % divisor == remainder |
ModuloLong |
value % divisor == remainder |
| Predicate | Description |
|---|---|
FiniteFloatPredicate |
float is finite |
FiniteDoublePredicate |
double is finite |
NonNaNFloatPredicate |
float is not NaN (Infinity allowed) |
NonNaNDoublePredicate |
double is not NaN (Infinity allowed) |
| Predicate | Description |
|---|---|
NotEmpty |
string not empty |
NotBlank |
string not blank (Unicode whitespace) |
LengthAtLeast |
length >= minimum |
LengthAtMost |
length <= maximum |
LengthBetween |
minimum <= length <= maximum |
MatchesRegex |
matches the regex pattern |
StartsWith |
starts with prefix |
EndsWith |
ends with suffix |
Contains |
contains infix |
| Predicate | Description |
|---|---|
TrueValue |
value is true |
FalseValue |
value is false |
And |
all values in a list are true |
Or |
at least one value in a list is true |
Xor |
odd number of values in a list are true |
Nand |
not all values in a list are true |
Nor |
no values in a list are true |
OneOf |
exactly one value in a list is true |
| Predicate | Description |
|---|---|
IsDigitChar |
Unicode digit (Character.isDigit) |
IsLetterChar |
Unicode letter (Character.isLetter) |
IsLetterOrDigitChar |
Unicode letter or digit (Character.isLetterOrDigit) |
IsLowerCaseChar |
Unicode lower-case (Character.isLowerCase) |
IsUpperCaseChar |
Unicode upper-case (Character.isUpperCase) |
IsWhitespaceChar |
Unicode whitespace (Character.isWhitespace) |
IsSpecialChar |
not letter, digit, whitespace, or space |
| Predicate | Description |
|---|---|
MinSize |
size >= minimum |
MaxSize |
size <= maximum |
SizeBetween |
minimum <= size <= maximum |
SizeEqual |
size == expected |
ContainsElement |
collection contains expected element |
EmptyCollection |
collection is empty |
ForAllElements |
all elements satisfy predicate |
ExistsElement |
at least one element satisfies predicate |
HeadSatisfies |
first element satisfies predicate (non-empty list) |
LastSatisfies |
last element satisfies predicate (non-empty list) |
IndexSatisfies |
element at index satisfies predicate |
InitSatisfies |
init slice (all but last) satisfies predicate |
TailSatisfies |
tail slice (all but first) satisfies predicate |
CountMatches |
matching element count equals expected count |
AscendingList |
list is non-decreasing |
DescendingList |
list is non-increasing |
| Predicate | Description |
|---|---|
AllOf |
all delegated constraints must pass |
AnyOf |
at least one delegated constraint must pass |
Not |
delegated constraint must fail |
The full matrix is kept in docs/type-matrix.md.
- production baseline: Java 8
- Java 8+ supported
- verification:
./gradlew clean check./gradlew testJava8./gradlew javadoc./gradlew pitest
- Java 8 runtime compatibility is verified by the
testJava8toolchain task - mutation testing is enforced with a 95% kill threshold via PITest
- current build uses Java 8 source/target compatibility and Gradle
--release 8when running on JDK 9+
See docs/compatibility.md for Java version caveats.
- current version:
1.1.0 - distribution: Maven Central
- release notes live in CHANGELOG.md
- contribution guide: CONTRIBUTING.md
- code of conduct: CODE_OF_CONDUCT.md
- security policy: SECURITY.md
MIT. See LICENSE.