Working with Versions
This library strictly follows the Semantic Versioning 2.0.0 specification. Invalid versions throw VersionParseException (an unchecked exception).
The Version class is an immutable value object made up of a major, minor and patch component plus optional pre-release and build-metadata data. It implements Comparable<Version> and is the entry point for everything else in the library.
Parsing Versions
"") when absent.// Strict parsing, throws VersionParseException on invalid input
Version v1 = Version.parse("1.2.3");
Version v2 = Version.parse("1.2.3-alpha.1");
Version v3 = Version.parse("1.2.3+build.20240101");
Version v4 = Version.parse("1.2.3-alpha.1+build.20240101");
// Non-throwing, returns an empty Optional instead of throwing
Optional<Version> v5 = Version.parseOptional("1.0.0");
// Validity check
boolean ok = Version.isValid("1.2.3"); // true
// Lenient parsing, tolerates a leading "v"/"=", surrounding whitespace and leading zeros
Version v6 = Version.parseLoose(" =v01.02.03 "); // 1.2.3Creating Versions Programmatically
Version basic = Version.of(1, 2, 3);
Version preRelease = Version.of(1, 2, 3, "beta");
Version withBuild = Version.of(1, 2, 3, "", "build.001");
Version full = Version.of(1, 2, 3, "rc.1", "build.002");Or with the builder:
Version v = Version.builder()
.withMajor(1)
.withMinor(2)
.withPatch(3)
.withPreRelease("rc.1")
.withMeta("build.002")
.build();Accessors
Version v = Version.of(2, 1, 0, "beta.2", "build.20240321");
v.getMajor(); // 2L
v.getMinor(); // 1L
v.getPatch(); // 0L
v.getPreRelease(); // "beta.2"
v.getPreReleaseIdentifiers(); // ["beta", "2"] (unmodifiable List<String>)
v.getBuildMetadata(); // "build.20240321"
v.hasPreRelease(); // true
v.hasBuildMetadata(); // true
v.getVersion(); // "2.1.0" (major.minor.patch only)
v.getVersionFull(); // "2.1.0-beta.2+build.20240321"
v.toString(); // "2.1.0-beta.2+build.20240321"Pre-release helpers
Version v = Version.parse("1.0.0-beta.1");
v.isAlpha(); // false, pre-release contains "alpha"
v.isBeta(); // true, pre-release contains "beta"
v.isDev(); // false, pre-release contains "dev"/"develop"/"development"
v.isRC(); // false, pre-release contains "rc"
v.isSnapshot(); // false, pre-release contains "snapshot"
Version.parse("1.0.0-SNAPSHOT").isSnapshot(); // true
Version.parse("2.1.0-RC.2").isRC(); // trueComparing Versions
Comparison follows SemVer precedence rules. Build metadata is ignored by default for precedence.
Version v1 = Version.parse("1.0.0");
Version v2 = Version.parse("1.0.1");
v2.isGreaterThan(v1); // true (>)
v2.isAtLeast(v1); // true (>=)
v1.isLessThan(v2); // true (<)
v1.isAtMost(v2); // true (<=)
v1.isEqualTo(v2); // false (precedence equality)
v1.compareTo(v2); // negative int (Comparable)isEqualTo compares precedence (ignoring build metadata), while equals compares structural equality (including build metadata). So 1.0.0+build.1 and 1.0.0+build.2 are isEqualTo each other but not equals.
Sorting
Version is Comparable, and so it sorts naturally:
List<Version> versions = List.of(
Version.parse("2.0.0"),
Version.parse("1.0.0"),
Version.parse("1.0.0-rc.1")
);
Collections.sort(versions); // ascending: 1.0.0-rc.1, 1.0.0, 2.0.0
versions.sort(Comparator.reverseOrder()); // descendingNeed build metadata as a deterministic tiebreak? Use the Version.BUILD_AWARE comparator:
versions.sort(Version.BUILD_AWARE);Coercing & Cleaning
coerce extracts the first version like value from an arbitrary string. clean normalizes a loosely formatted (but valid) version string. Both return an Optional.
Version.coerce("v1.2"); // Optional[1.2.0]
Version.coerce("app-1.2.3.jar"); // Optional[1.2.3]
Version.coerce("release 4"); // Optional[4.0.0]
Version.coerce("no version here"); // Optional.empty
Version.clean(" =v1.2.3 "); // Optional["1.2.3"]
Version.clean("not a version"); // Optional.emptyValidation & Error Handling
// All throw VersionParseException
Version.parse("1.02.03"); // leading zeros
Version.parse("1.2.3-"); // empty pre-release
Version.parse("1.2.3+"); // empty build metadata
Version.parse("1.2.3-alpha..1"); // empty identifier
Version.parse("1.a.3"); // non-numeric componenttry {
Version invalid = Version.parse("1.a.3");
} catch (VersionParseException e) {
System.err.println("Invalid version: " + e.getMessage());
}
// Or avoid exceptions entirely
Optional<Version> maybe = Version.parseOptional("1.a.3"); // Optional.emptyNext up, have a look at Ranges.