Skip to Content
JavasemverVersions

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

Pre-release and build-metadata fields default to empty strings ("") 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.3

Creating 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(); // true

Comparing 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()); // descending

Need 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.empty

Validation & Error Handling

Invalid versions throw VersionParseException when parsed strictly.
// 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 component
try { 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.empty

Next up, have a look at Ranges.

Last updated on