In Maven/Ivy-style dependency version ranges written like [1.0,2.0) or (,1.5], what do the square brackets versus parentheses mean, and how does this differ from npm's caret/tilde model?
answer
- [ ] inclusive, ( ) exclusive
- single bracket = exact pin
- open bound = unbounded side
- ranges hit repo metadata at resolve time
- nearest-wins mediation can override a range
basics
~10 sSquare brackets mean 'include this exact boundary,' parentheses mean 'up to but not including it.' So [1.0,2.0) means from 1.0 up to (not including) 2.0. It's a math-style interval instead of npm's shorthand symbols.
solid answer
~40 sMaven-family version ranges borrow mathematical interval notation: [ and ] are inclusive bounds, ( and ) are exclusive bounds, and either side can be omitted for an open-ended range. [1.0,2.0) means >=1.0 and <2.0; (,1.5] means anything up to and including 1.5. This is more expressive than npm's caret/tilde shorthand because you can express arbitrary bounds — e.g. excluding a known-bad release with [1.0,1.5),(1.5,2.0) — but it's also more verbose for the common case, which is why caret/tilde exist as sugar for 'the range semver says is safe.' Both models ultimately compile down to the same idea: a resolver picks the version that satisfies the interval.
go deeper
Should be able to read a given interval expression like [1.0,2.0) and state which versions satisfy it; doesn't need to have used Maven ranges directly.
Should contrast interval notation with caret/tilde shorthand and explain why one is more expressive but more verbose, and know that open-ended ranges don't self-limit the way caret does.
Should discuss the operational cost of floating ranges (repository metadata lookups, non-reproducible resolution) and describe a realistic conflict/mediation failure mode in a multi-module build.
Should weigh interval ranges against org-wide policy — e.g. whether to allow ranges at all in published artifacts, and how that interacts with reproducible-build and supply-chain requirements.
## Reading the notation Interval-notation ranges, used by Maven, Ivy, and OSGi, describe a version constraint the way mathematics describes a set of real numbers: a pair of bounds and a marker for whether each bound is included. A **square bracket**, `[` or `]`, is an inclusive (closed) boundary; a **parenthesis**, `(` or `)`, is an exclusive (open) boundary. - `[1.0,2.0)` reads as 'greater than or equal to 1.0, strictly less than 2.0' — 1.0 itself is allowed, 2.0 is not. - `(1.0,2.0]` flips that: 1.0 is excluded, 2.0 is allowed. - A single-sided range omits one bound: `(,1.5]` means 'anything up to and including 1.5, no lower limit,' and `[1.5,)` means '1.5 or anything newer, no upper limit.' - A single bracketed value with no comma, like `[1.5]`, means an exact-match requirement — only that version satisfies it, which is the interval-notation equivalent of a hard pin. ## A different design philosophy from caret and tilde This is a different design philosophy from npm's caret/tilde shorthand. Caret and tilde are sugar layered on top of semver's specific promise — 'patch/minor bumps within a major are safe' — so they only ever express the two ranges that promise implies. Interval notation makes no assumption about what a version number means; it just describes an arbitrary set of acceptable values, which is why it can express things caret/tilde cannot, such as excluding a specific known-bad release in the middle of an otherwise fine range, or spanning multiple majors deliberately. The cost of that flexibility is verbosity for the common case: expressing 'anything semver-compatible above 1.2.3' takes one character in npm (`^1.2.3`) but requires the consumer to already know and write out the next major's lower bound in Maven-style notation (`[1.2.3,2.0.0)`). ## The problem both systems solve Both systems exist to solve the same underlying problem: a dependency graph where each library specifies not one fixed version of its own dependencies but a set of versions it's known to work with, so a build tool can find a single version per dependency that satisfies every consumer simultaneously. Without ranges, every library in a graph would have to agree on the exact same pinned version of every shared dependency, which is untenable once a project pulls in dozens of independently-versioned libraries — the classic **'diamond dependency'** problem, where library A needs `commons-lang` 2.4 and library B needs `commons-lang` 2.6, and only a range-aware resolver can reconcile that into one chosen version that hopefully satisfies both. ## The cost of letting a range float The trade-off with interval ranges specifically is that Maven's resolution algorithm for them is not as widely used or as battle-tested in day-to-day workflows as fixed-version dependencies, largely because Maven Central discourages true floating ranges for published artifacts — a range has to hit the repository metadata to enumerate available versions on every resolution, which is slower and network-dependent compared to a fixed coordinate resolved once and cached. Gradle and Ivy support ranges more fluidly, but the same caution applies broadly across the JVM ecosystem: a floating range means the build's output can change without any change to the build file, which undermines the reproducibility that many teams value more than automatic freshness. ## Failure modes Failure modes mirror npm's but with their own JVM-specific flavor. 1. **'Version range resolution' failures** happen when the interval, once expanded against the actual repository metadata, yields either zero candidates (e.g. the range excludes every published version because of an off-by-one bound) or a set that conflicts irreconcilably with another dependency's range, causing the build to fail at resolve time rather than at runtime. 2. **A subtler failure is silent:** a build using an open-ended range like `[1.0,)` picks up a new major version the moment it's published, because nothing in the syntax stops it — unlike npm's caret, which always caps at the next major by convention. A team that wants caret-like safety with interval notation has to write it explicitly: `[1.0,2.0)`. ## A concrete scenario A concrete, realistic scenario is a multi-module enterprise Java project where module A pins a logging library range `[1.2,1.4)` to dodge a known-broken 1.4.0 release, while module B in the same reactor build declares no range at all and pulls in whatever 'newest' resolves elsewhere in the tree; Maven's nearest-wins mediation then silently picks whichever declaration is closer in the dependency tree, and the team only discovers the mismatch when the excluded 1.4.0 behavior shows up in production despite the range that was supposed to prevent it.
- How would you write a Maven version range that excludes a single known-broken release, say 1.4.0, from an otherwise acceptable 1.x range?You can express it as a union of two intervals: [1.0,1.4),(1.4,2.0) — everything from 1.0 up to but not including 1.4.0, plus everything strictly above 1.4.0 up to but not including 2.0.0. This kind of surgical exclusion is exactly what caret/tilde shorthand can't express, since they only ever describe one contiguous 'safe' band.
- Why does Maven Central generally discourage publishing artifacts that depend on floating version ranges?A floating range forces every consumer's build to hit repository metadata to enumerate available versions at resolve time instead of fetching one known coordinate, which is slower, network-dependent, and can make the same build produce different results on different days. It also breaks the reproducibility guarantee that a pinned, immutable artifact coordinate is supposed to provide once published.
- What happens if two modules in the same Maven reactor build declare conflicting version ranges for the same transitive dependency?Maven doesn't fail immediately just because ranges differ; its dependency mediation picks one version — nearest declaration in the tree wins by default — and that chosen version simply has to satisfy the ranges it's checked against, or resolution fails. This can silently pick a version one module explicitly tried to exclude via its range, since the mediation isn't range-aware in the way a SAT-style resolver would be.
It's the same notation your high-school math teacher used for interval sets — [1,2) means 'from 1, up to but not touching 2' — just applied to version numbers instead of real numbers.
saying these in an interview costs you the question
- Thinks brackets and parentheses are interchangeable notation for the same thing
- Doesn't know a single bracketed version like [1.5] means an exact pin
- Assumes interval ranges always cap at the next major automatically like npm's caret
- Can't explain why floating ranges are slower to resolve than fixed coordinates