Repository navigation
VersionComparator
Basti edited this page Sep 27, 2026
·
2 revisions
Need deterministic version math beyond String#compareTo? VersionComparator handles semantic-ish versions with suffixes and prerelease tags.
- Compare arbitrary numeric segment counts (
1.2.0.5vs1.2). - Understand prerelease labels like
-alpha,-beta2, and-rc1. - Keep version comparisons consistent across update checks, matchers, and commands.
- Supports letter suffixes appended to base versions (
1.0a>1.0). - Unknown prerelease labels (e.g.,
preview3) still compare sanely. - Build metadata (
build.30in26.3.build.30-alpha, or anything after+) never corrupts the numeric version - it's only used as a last-resort tie-breaker. - Numbers of any length compare without overflow (
1.0.3000000000>1.0.2).
The format TurtleLib's own plugins publish, and the one to use for the running plugin's version and every version number on Modrinth/Hangar:
-
MAJOR.MINOR.PATCH, digits only (1.4.0). More or fewer segments work too (1.2.0.5,1.2). - Optional prerelease suffix after a dash:
alpha,beta,preorrcwith its number (1.4.0-beta2,1.4.0-rc1). - Optional build number as trailing segments (
26.3.build.30).
Other common spellings are read like their counterpart in that format:
| Spelling | Read as |
|---|---|
v1.4.0, V1.4.0
|
1.4.0 |
1.4.0-beta.2, 1.4.0-beta-2, 1.4.0-beta_2
|
1.4.0-beta2 |
1.4.0beta2, 1.4.0rc1, 1.4.0beta.2
|
1.4.0-beta2, 1.4.0-rc1: alpha, beta, pre and rc glued to the number are prereleases |
1.4.0-10 |
A prerelease numbered 10; number-only labels compare numerically (-10 > -2) |
1.4.0+build.5, 1.4.0+5
|
1.4.0 with build metadata 5: only breaks ties (1.4.0+5 > 1.4.0, but < 1.4.0.1) |
- Never rejects anything: every string gets some place in the order, without an error or a log line. Stick to the format above.
-
Equal isn't identical: missing and empty segments count as
0(1..2=1.0.2), and leading zeros, case, avprefix, surrounding spaces and the separator before a prerelease number are ignored, socompare(...) == 0doesn't mean the strings are the same. -
Known prerelease labels: only
alpha,beta,preandrc, in Minecraft's order. Any other label (snapshot,dev,preview) ranks afterrc. -
Dates with dashes:
2024-01-15is read as a prerelease of2024, so it ranks below2024(dates among themselves compare correctly). -
Letters mixed with digits in a main segment are dropped unless they start with
alpha,beta,preorrc:1.4.0a1=1.4.0. Other letters glued to the last segment count as a letter suffix, so1.4.0dev>1.4.0.
- Normalize empty/null version strings to
""(treated as0.0.0). - Use
VersionComparator.isGreater(latest, current)before prompting updates. - Reuse the static helper anywhere you need deterministic ordering.
| Method | Purpose |
|---|---|
compare(String a, String b) |
Returns <0, 0, or >0 for version ordering. |
isGreater(String a, String b) |
Convenience compare(a, b) > 0. |
The full rule table lives here and is pinned down by VersionComparatorTest:
- Missing numeric segments default to
0(1.2==1.2.0). - Letter suffixes on the base version rank above plain releases (
1.0b>1.0). - A version with no
-prereleasesuffix is a full release and outranks any prerelease of the same numeric version. - Prerelease order, as in Minecraft:
alpha<beta<pre<rc< unknown labels < release (26.3-pre1<26.3-rc1<26.3). Unknown labels (e.g.snapshot,dev) and number-only labels rank afterrc- not beforealpha- so they're never mistaken for an earlier stage or for a full release. - Identical prerelease labels are ordered by what follows them, numbers numerically (
beta1<beta2<beta10,beta=beta0); two different unknown labels (same rank) fall back to lexicographic order, with a number-only label first. - A main-version segment that doesn't start with a digit (e.g.
buildin26.3.build.30) starts build metadata, not a fake0segment - it never shifts the numeric segments before it. So does everything after a+. Their numeric parts only matter as a final tie-breaker (1.2.build.31>1.2.build.30,1.2+31>1.2+30) when everything else is equal.
import net.kroet.turtlelib.helper.VersionComparator;
int result = VersionComparator.compare("1.2.0", "1.1.9");
boolean newer = VersionComparator.isGreater("1.2.0-beta2", "1.2.0-beta1");
boolean same = VersionComparator.compare("1.2", "1.2.0") == 0;- The class is static—no need to instantiate anything or keep global state.
- Powers
ServerMatcher's range checks. - Helps
UpdateCheckerdecide whether remote releases truly outrank the currently running version.