Skip to content

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.

Why Use It? 🎯

  • Compare arbitrary numeric segment counts (1.2.0.5 vs 1.2).
  • Understand prerelease labels like -alpha, -beta2, and -rc1.
  • Keep version comparisons consistent across update checks, matchers, and commands.

Highlights ✨

  • Supports letter suffixes appended to base versions (1.0a > 1.0).
  • Unknown prerelease labels (e.g., preview3) still compare sanely.
  • Build metadata (build.30 in 26.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).

Version Format 📏

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, pre or rc with 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)

Limitations ⚠️

  • 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, a v prefix, surrounding spaces and the separator before a prerelease number are ignored, so compare(...) == 0 doesn't mean the strings are the same.
  • Known prerelease labels: only alpha, beta, pre and rc, in Minecraft's order. Any other label (snapshot, dev, preview) ranks after rc.
  • Dates with dashes: 2024-01-15 is read as a prerelease of 2024, so it ranks below 2024 (dates among themselves compare correctly).
  • Letters mixed with digits in a main segment are dropped unless they start with alpha, beta, pre or rc: 1.4.0a1 = 1.4.0. Other letters glued to the last segment count as a letter suffix, so 1.4.0dev > 1.4.0.

Quick Start 🚀

  1. Normalize empty/null version strings to "" (treated as 0.0.0).
  2. Use VersionComparator.isGreater(latest, current) before prompting updates.
  3. Reuse the static helper anywhere you need deterministic ordering.

References 📚

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.

Comparison Rules 🧠

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 -prerelease suffix 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 after rc - not before alpha - 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. build in 26.3.build.30) starts build metadata, not a fake 0 segment - 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.

Examples 🧪

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;

Tips & Notes 💡

  • The class is static—no need to instantiate anything or keep global state.

Relationships to Other Helpers 🔗

  • Powers ServerMatcher's range checks.
  • Helps UpdateChecker decide whether remote releases truly outrank the currently running version.

Clone this wiki locally