A Java library for parsing and working with versions and version constraints. Versions and constraints can be expressed in a number of common formats. The parsed versions can be queried for their components and are ordered. Operations on parsed constraints include testing whether a given version satisfies the constraint and set operations such as intersection and union.
The following version and version constraint schemes are supported:
See the examples folder for complete working code demonstrating the usage of this library. The library is available from Maven Central using the following Maven dependency:
<dependency>
<groupId>org.cthing</groupId>
<artifactId>versionparser</artifactId>
<version>5.0.0</version>
</dependency>
or the following Gradle dependency:
implementation("org.cthing:versionparser:5.0.0")
Scheme | Version Factory | Version Constraint Factory |
---|---|---|
Calendar | CalendarVersionScheme.parse(String) 1 |
N/A |
Gradle | GradleVersionScheme.parseVersion(String) |
GradleVersionScheme.parseConstraint(String) |
Java | JavaVersionScheme.parseVersion(String) |
JavaVersionScheme.parseRange(String) |
Maven | MvnVersionScheme.parseVersion(String) |
MvnVersionScheme.parseConstraint(String) |
Npm | NpmVersionScheme.parseVersion(String) |
NpmVersionScheme.parseConstraint(String) |
RubyGems | GemVersionScheme.parseVersion(String) |
GemVersionScheme.parseConstraint(String) |
Semantic | SemanticVersion.parseVersion(String) |
N/A |
1 A CalendarVersionScheme
instance must be created to define the version format. Call the parse
method
on that instance to create a version instance.
All version factories create an instance of a scheme-specific version class (e.g. MvnVersion
) that implements the
Version
interface. All version constraint factories create an instance of the VersionConstraint
class. A
VersionConstraint
class consists of one or more VersionRange
instances. If desired, both the VersionConstraint
and VersionRange
classes can be directly constructed. Do not mix version schemes (e.g. do not use Maven version
instances to create NPM version constraints).
Support is provided for parsing Maven versions and dependency version constraints.
// Parse versions
final MvnVersion version1 = MvnVersionScheme.parseVersion("1.2.3");
final Version version2 = MvnVersionScheme.parseVersion("2.0.7");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3");
assertThat(version1.isPreRelease()).isFalse();
assertThat(version1.getComponents()).containsExactly("1", "2", "3");
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(-1);
// Parse version constraints
final VersionConstraint constraint1 = MvnVersionScheme.parseConstraint("[1.0.0,2.0.0)");
final VersionConstraint constraint2 = MvnVersionScheme.parseConstraint("[1.5.0,3.0.0)");
// Perform constraint checking
assertThat(constraint1.allows(version1)).isTrue();
assertThat(constraint1.allows(version2)).isFalse();
// Perform constraint set operations
assertThat(constraint1.intersect(constraint2)).isEqualTo(MvnVersionScheme.parseConstraint("[1.5.0,2.0.0)"));
assertThat(constraint1.union(constraint2)).isEqualTo(MvnVersionScheme.parseConstraint("[1.0.0,3.0.0)"));
Support is provided for parsing Gradle versions and dependency version constraints.
// Parse versions
final GradleVersion version1 = GradleVersionScheme.parseVersion("1.2.3-SNAPSHOT");
final Version version2 = GradleVersionScheme.parseVersion("2.0.7");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3-SNAPSHOT");
assertThat(version1.isPreRelease()).isTrue();
assertThat(version1.getComponents()).containsExactly("1", "2", "3", "SNAPSHOT");
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(-1);
// Parse version constraints
final VersionConstraint constraint1 = GradleVersionScheme.parseConstraint("[1.0.0,2.0.0[");
final VersionConstraint constraint2 = GradleVersionScheme.parseConstraint("[1.5.0,2.+]");
// Perform constraint checking
assertThat(constraint1.allows(version1)).isTrue();
assertThat(constraint1.allows(version2)).isFalse();
// Perform constraint set operations
assertThat(constraint1.intersect(constraint2)).isEqualTo(GradleVersionScheme.parseConstraint("[1.5.0,2.+]"));
assertThat(constraint1.union(constraint2)).isEqualTo(GradleVersionScheme.parseConstraint("[1.0.0,2.0.0)"));
Support is provided for parsing versions of the Java programming language and creating constraints based on those versions. During its long history, Java has used different versioning schemes (e.g. 1.4.2_151, 17.0.12+34).
// Parse versions
final JavaVersion version1 = JavaVersionScheme.parseVersion("17.0.11+9");
final Version version2 = GradleVersionScheme.parseVersion("1.4.2_151");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("17.0.11+9");
assertThat(version1.isPreRelease()).isFalse();
assertThat(version1.getFeature()).isEqualTo(17);
assertThat(version1.getInterim()).isEqualTo(0);
assertThat(version1.getUpdate()).isEqualTo(11);
assertThat(version1.getBuild()).contains(9);
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(1);
// Parse version constraints
final VersionConstraint constraint1 = GradleVersionScheme.parseConstraint("[11,21)");
final VersionConstraint constraint2 = GradleVersionScheme.parseConstraint("[1.5,1.7]");
// Perform constraint checking
assertThat(constraint1.allows(version1)).isTrue();
assertThat(constraint1.allows(version2)).isFalse();
// Perform constraint set operations
assertThat(constraint1.union(constraint2)).isEqualTo(GradleVersionScheme.parseConstraint("[1.5,1.7],[11,21)"));
// Test current runtime Java version
assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17, JavaVersion.RUNTIME_VERSION)).isTrue();
// Test a Java version is greater than or equal to Java 17
assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "21")).isTrue();
Support is provided for parsing semantic versions and NPM dependency version constraints.
// Parse versions
final SemanticVersion version1 = NpmVersionScheme.parseVersion("1.2.3");
final Version version2 = NpmVersionScheme.parseVersion("2.0.7");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3");
assertThat(version1.isPreRelease()).isFalse();
assertThat(version1.getMajor()).isEqualTo(1);
assertThat(version1.getMinor()).isEqualTo(2);
assertThat(version1.getPatch()).isEqualTo(3);
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(-1);
// Parse version constraints
final VersionConstraint constraint1 = NpmVersionScheme.parseConstraint("^1.0.0");
final VersionConstraint constraint2 = NpmVersionScheme.parseConstraint(">=1.5.0 <3.0.0");
// Perform constraint checking
assertThat(constraint1.allows(version1)).isTrue();
assertThat(constraint1.allows(version2)).isFalse();
// Perform constraint set operations
assertThat(constraint1.intersect(constraint2)).isEqualTo(NpmVersionScheme.parseConstraint(">=1.5.0 <2.0.0-0"));
assertThat(constraint1.union(constraint2)).isEqualTo(NpmVersionScheme.parseConstraint(">=1.0.0 <3.0.0"));
Support is provided for parsing RubyGems versions and version requirements.
// Parse versions
final GemVersion version1 = GemVersionScheme.parseVersion("1.2.3");
final Version version2 = GemVersionScheme.parseVersion("2.0.7");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3");
assertThat(version1.isPreRelease()).isFalse();
assertThat(version1.getComponents()).containsExactly("1", "2", "3");
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(-1);
// Parse version constraints
final VersionConstraint constraint1 = GemVersionScheme.parseConstraint("~>1.0");
final VersionConstraint constraint2 = GemVersionScheme.parseConstraint(">=1.5.0", "<3.0.0");
// Perform constraint checking
assertThat(constraint1.allows(version1)).isTrue();
assertThat(constraint1.allows(version2)).isFalse();
// Perform constraint set operations
assertThat(constraint1.intersect(constraint2)).isEqualTo(GemVersionScheme.parseConstraint(">=1.5.0", "<2.ZZZ"));
assertThat(constraint1.union(constraint2)).isEqualTo(GemVersionScheme.parseConstraint(">=1.0.0", "<3.0.0"));
Support is provided for parsing semantic versions.
// Parse versions
final SemanticVersion version1 = NpmVersionScheme.parseVersion("1.2.3");
final SemanticVersion version2 = NpmVersionScheme.parseVersion("2.0.7");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3");
assertThat(version1.isPreRelease()).isFalse();
assertThat(version1.getMajor()).isEqualTo(1);
assertThat(version1.getMinor()).isEqualTo(2);
assertThat(version1.getPatch()).isEqualTo(3);
// Verify ordering
assertThat(version1.compareTo(version2)).isEqualTo(-1);
Pre-release and build metadata is supported, and a "v" prefix is ignored.
final SemanticVersion version = SemanticVersion.parse("v1.2.3-beta.1+56709");
assertThat(version.getOriginalVersion()).isEqualTo("v1.2.3-beta.1+56709");
assertThat(version).hasToString("v1.2.3-beta.1+56709");
assertThat(version.getNormalizedVersion()).isEqualTo("1.2.3-beta.1+56709");
assertThat(version.getCoreVersion()).isEqualTo("1.2.3");
assertThat(version.isPreRelease()).isTrue();
assertThat(version.getMajor()).isEqualTo(1);
assertThat(version.getMinor()).isEqualTo(2);
assertThat(version.getPatch()).isEqualTo(3);
assertThat(version.getPreReleaseIdentifiers()).containsExactly("beta", "1");
assertThat(version.getBuild()).containsExactly("56709");
A convenience parsing method is provided for the common case of specifying a core version and a separate pre-release identifier.
final SemanticVersion version = SemanticVersion.parse("1.2.3", "beta.1");
assertThat(version.getOriginalVersion()).isEqualTo("1.2.3-beta.1");
assertThat(version).hasToString("1.2.3-beta.1");
assertThat(version.getNormalizedVersion()).isEqualTo("1.2.3-beta.1");
assertThat(version.getCoreVersion()).isEqualTo("1.2.3");
assertThat(version.isPreRelease()).isTrue();
assertThat(version.getMajor()).isEqualTo(1);
assertThat(version.getMinor()).isEqualTo(2);
assertThat(version.getPatch()).isEqualTo(3);
assertThat(version.getPreReleaseIdentifiers()).containsExactly("beta", "1");
Another convenience parsing method is provided to accommodate snapshot builds. This method takes a core version and a flag to indicate whether the version represents a snapshot. Snapshot versions are given a pre-release component which is the number of milliseconds since the Unix Epoch.
final SemanticVersion version1 = SemanticVersion("1.2.3", true);
assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3-1717386681940");
assertThat(version1).hasToString("1.2.3-1717386681940");
assertThat(version1.getNormalizedVersion()).isEqualTo("1.2.3-1717386681940");
assertThat(version1.getCoreVersion()).isEqualTo("1.2.3");
assertThat(version1.isPreRelease()).isTrue();
assertThat(version1.getMajor()).isEqualTo(1);
assertThat(version1.getMinor()).isEqualTo(2);
assertThat(version1.getPatch()).isEqualTo(3);
assertThat(version1.getPreReleaseIdentifiers()).containsExactly("1717386681940");
final SemanticVersion version2 = SemanticVersion("1.2.3", false);
assertThat(version2.getOriginalVersion()).isEqualTo("1.2.3");
assertThat(version2).hasToString("1.2.3");
assertThat(version2.getNormalizedVersion()).isEqualTo("1.2.3");
assertThat(version2.getCoreVersion()).isEqualTo("1.2.3");
assertThat(version2.isPreRelease()).isFalse();
assertThat(version2.getMajor()).isEqualTo(1);
assertThat(version2.getMinor()).isEqualTo(2);
assertThat(version2.getPatch()).isEqualTo(3);
assertThat(version2.getPreReleaseIdentifiers()).isEmpty();
Support is provided for parsing calendar versions.
// Parse a single version
final CalendarVersion version1 = CalendarVersionScheme.parse("YYYY.MM.0D-MAJOR", "2023.2.03-4");
// Parse multiple versions using the same format
final CalendarVersionScheme scheme = new CalendarVersionScheme("yyyy.major.minor");
final Version version2 = scheme.parse("2022.1.0");
final Version version3 = scheme.parse("2022.1.1");
// Obtain information from the parsed version
assertThat(version1.getOriginalVersion()).isEqualTo("2023.2.03-4");
assertThat(version1.isPreRelease()).isFalse();
// Obtain information about the first component of the version
final Component component1 = version1.getComponents().get(0);
assertThat(component1.getValueStr()).isEqualTo("2023");
assertThat(component1.getValue()).isEqualTo(2023);
assertThat(component1.getCategory()).isEqualTo(ComponentCategory.YEAR);
// Verify ordering
assertThat(version2.compareTo(version3)).isEqualTo(-1);
In preparation for creating this library, a survey of many popular versioning schemes was conducted. Among other things, this lead to the recognition that all version constraint specifications could be expressed using a single notation. This in turn allows version constraints to be handled in a version scheme independent manner. To work with a constraint, the only requirement of a version is that it be ordered.
The library is compiled for Java 17. If a Java 17 toolchain is not available, one will be downloaded.
Gradle is used to build the library:
./gradlew build
The Javadoc for the library can be generated by running:
./gradlew javadoc
This project is released on the Maven Central repository. Perform the following steps to create a release.
- Commit all changes for the release
- In the
build.gradle.kts
file, edit theProjectVersion
object- Set the version for the release. The project follows semantic versioning.
- Set the build type to
BuildType.release
- Commit the changes
- Wait until CI builds the release candidate
- Run the command
mkrelease versionparser <version>
- In a browser go to the Maven Central Repository Manager
- Log in
- Use the
Staging Upload
to upload the generated artifact bundleversionparser-bundle-<version>.jar
- Click on
Staging Repositories
- Once it is enabled, press
Release
to release the artifacts to Maven Central - Log out
- Wait for the new release to be available on Maven Central
- In a browser, go to the project on GitHub
- Generate a release with the tag
<version>
- In the build.gradle.kts file, edit the
ProjectVersion
object- Increment the version patch number
- Set the build type to
BuildType.snapshot
- Update the
CHANGELOG.md
with the changes in the release and prepare for next release changes - Update the
Usage
section in theREADME.md
with the latest artifact release version - Commit these changes