Semantic Versioning & Conflicts - Java

  1. Which interpretation is most accurate under Semantic Versioning?
    Answer: The assumption is unsafe because major version zero indicates an unstable public API that may change Semantic Versioning (SemVer) questions test whether you understand how version numbers communicate stability and compatibility guarantees — not just how to increment them mechanically. The critical insight here is that SemVer treats major version zero (0.y.z) as a special case. Under SemVer, a major version of 0 explicitly signals that the API is unstable and under initial development. The spec states that anything may change at any time during this phase — there are no backward-compatibility guarantees whatsoever. This means a minor bump from 0.4.8 to 0.5.0 carries no promise of compatibility, making the developer's auto-acceptance assumption genuinely unsafe. D is correct. A is the most tempting wrong answer because it sounds like solid SemVer knowledge — but it misapplies the rule. The "minor versions preserve compatibility" guarantee only applies once you've reached 1.0.0 and beyond. Before that threshold, the stability guarantee simply doesn't exist. B is wrong on its face: in stable SemVer (1.0.0+), major versions signal breaking changes, minor versions signal new features, and patch versions signal bug fixes. Claiming only major and patch numbers matter is a fabricated rule with no SemVer basis. C confuses "implementation detail" with "public API." Removing a public class is explicitly a breaking change to the public API regardless of version — deleting something consumers depend on is never an implementation-only concern. For exam questions involving versioning, always check whether the major version is zero. That single digit changes the entire compatibility story.
  2. What is the best explanation and next step?
    Answer: Library B still supplies that version; inspect all paths and constrain or exclude it appropriately When working with dependency management in Java build tools like Maven or Gradle, it's important to remember that exclusions are scoped to a single dependency declaration — they don't propagate globally across your entire dependency graph. Here's what happened: the developer correctly excluded logging:1.2.0 from the transitive path through library A, but library B has its own independent transitive dependency on logging:1.2.0. Because B's path was never touched, the build tool still resolves logging:1.2.0 from that route. The artifact remains on the classpath, and the dependency report accurately reflects this. B is correct — you must inspect all paths that pull in the problematic library and either exclude it from each one or use a version constraint (like Maven's <dependencyManagement> or Gradle's resolutionStrategy) to force a fixed version globally. A is flat-out wrong — transitive exclusions absolutely work in both Maven and Gradle; the issue here is incomplete application, not a tool limitation. C invents a rule that doesn't exist: exclusions have nothing to do with major version numbers; they match on groupId and artifactId. D is also wrong — the dependency report is not stale, and compiling does nothing to remove unwanted JARs; the report faithfully reflects what the resolver found. A useful study tip: whenever you see a dependency "still appearing" after an exclusion, think multiple supply paths. The safest fix is usually a global version constraint rather than hunting down every exclusion point individually — that scales much better across large dependency trees.
  3. Under Semantic Versioning, what is the minimum appropriate version for the next release?
    Answer: 2.0.0, because removing the public method is a breaking API change When a question mentions Semantic Versioning (SemVer), your first instinct should be to classify every change by its most disruptive impact. SemVer uses a MAJOR.MINOR.PATCH structure, and the version number you choose must reflect the highest-severity change in the release — you can't average them out or pick the majority. Here, three changes are bundled together: a bug fix (patch-level), an added overload (minor-level), and the removal of a public method (breaking change). That last one is the decisive factor. Removing a public method means existing compiled clients — code that was already working — can break at runtime. That is the textbook definition of a breaking change, which mandates a MAJOR version bump. Starting from 1.7.9, the correct next version is 2.0.0, resetting MINOR and PATCH to zero per SemVer rules. A is wrong because it only accounts for the bug fix and ignores both the new overload and — critically — the removal of the public method. A patch increment is never appropriate when breaking changes are present. B is tempting but incorrect: even though the overload is backward-compatible, the removed method breaks backward compatibility regardless of its prior deprecation status. Deprecation warns users; removal breaks them. D misunderstands SemVer entirely — build metadata (the + suffix) communicates build information like commit hashes and carries no semantic meaning about compatibility. You cannot use it to signal API changes. The key study tip: in SemVer, one breaking change overrides everything else. Always scan all changes first, then version for the worst one.
  4. Which core version is selected, and what risk remains?
    Answer: core:1.5.0 is selected; framework may fail if it requires APIs available only in 2.0.0 When Maven encounters the same dependency declared at multiple points in the dependency graph, it applies nearest-definition mediation: the version closest to the root of the graph (fewest hops away) wins. This is the core concept being tested here. In this scenario, your project declares core:1.5.0 directly — that's one hop from the root. The transitive path through framework:3.0.0 brings in core:2.0.0 at two hops. Since core:1.5.0 is nearer, Maven selects it. That confirms A is correct. The lurking danger is that framework:3.0.0 was built and tested against core:2.0.0, which may expose new APIs or changed behavior. At runtime, framework will find only core:1.5.0 on the classpath, potentially triggering NoSuchMethodError or similar failures if it calls anything that didn't exist in the older version. B is wrong because Maven does not use version numbers as a tiebreaker — it uses graph distance. "Highest version wins" is how npm resolves conflicts, not Maven. C is wrong because the JVM has a single, flat classpath; only one version of any class can be loaded by the default classloader. Maven does not create isolated classloaders per dependency the way OSGi does — both versions are never simultaneously active. D is wrong because Maven does not reject graphs with version conflicts at all; it silently picks a winner via mediation rules. Your study tip: remember the phrase "nearest definition wins" for Maven. When you see a conflict scenario, count the hops — whichever declaration is closer to the root project takes precedence, regardless of version number.
  5. Which sequence lists these versions from lowest to highest Semantic Versioning precedence?
    Answer: 1.0.0-alpha.2, 1.0.0-alpha.10, 1.0.0-beta, then 1.0.0 When a question involves Semantic Versioning (SemVer) ordering, focus on two core rules: pre-release versions (anything with a hyphen like -alpha or -beta) always rank lower than the release version, and within pre-release identifiers, numeric identifiers are compared as integers, not strings. That second rule is the key to this question. When you compare alpha.2 and alpha.10, you might be tempted to sort them lexicographically — where "10" comes before "2" because "1" < "2" alphabetically. But SemVer explicitly says numeric identifiers are compared numerically, so 2 < 10, meaning alpha.2 has lower precedence than alpha.10. From there, alpha identifiers precede beta identifiers alphabetically, and any pre-release version precedes the stable release 1.0.0. This gives you the correct lowest-to-highest order: 1.0.0-alpha.2 → 1.0.0-alpha.10 → 1.0.0-beta → 1.0.0, which is answer A. Answer B incorrectly places alpha.10 before alpha.2, treating the numeric identifiers as strings rather than integers — the classic lexicographic trap. Answer C reverses the entire order, listing the stable release first and descending to pre-release versions, which is highest-to-lowest precedence, not lowest-to-highest. Answer D scrambles the alpha versions, placing beta between them, which violates both the numeric comparison rule and the alphabetical ordering of pre-release labels. As a study tip, remember the mnemonic "numbers beat strings in SemVer" — whenever you see a numeric segment in a pre-release tag, always compare it as an integer, not character-by-character.
  6. What is the most likely result when the application reaches that method call?
    Answer: A linkage error such as NoSuchMethodError occurs when the missing method is invoked When working with Java dependency conflicts, it helps to clearly separate what happens at compile time versus runtime — these are two distinct phases with different classpath configurations, and that distinction is exactly what this question tests. At compile time, the Java compiler verified that Codec.encode(byte[]) exists in version 2.1.0 and produced valid bytecode containing a symbolic reference to that method. The compiler's job is done — it trusts the method will be available later. At runtime, however, the JVM loads version 1.6.0, which contains the Codec class but lacks that method. When the JVM tries to resolve the symbolic reference and execute the call, it discovers the method simply isn't there and throws a NoSuchMethodError. This makes C correct — a linkage error occurs at the moment of invocation. A is wrong because the Java compiler only reads the compile-time classpath; it has no knowledge of what will be on the runtime classpath and doesn't try to resolve it. B is incorrect because ClassNotFoundException would only occur if the Codec class itself were missing — but the class exists in 1.6.0, just without the newer method. Confusing a missing class with a missing method is a common trap. D is wrong because the JVM has no built-in mechanism to automatically resolve dependency conflicts or download newer versions at runtime — that's the job of a build tool like Maven or Gradle, not the JVM itself. A useful rule of thumb: if the class is missing, expect ClassNotFoundException; if the method is missing, expect NoSuchMethodError. Keeping these two runtime errors distinct will serve you well on any Java exam.
  7. Which statement best describes the expected resolution?
    Answer: Gradle selects 2.1.0, although the higher major version may be incompatible with a consumer When you see a Gradle dependency conflict question, focus on one core principle: Gradle's default conflict resolution strategy is newest-wins. It does not understand semantic versioning semantics — it simply picks the highest version string among all requested versions and uses that single version everywhere on the classpath. With that in mind, when parser:1.9.4 and parser:2.1.0 are both requested, Gradle compares the version strings, determines 2.1.0 is higher, and resolves the conflict by selecting 2.1.0 for the entire build. This is exactly what B describes — and the nuance B captures is important: Gradle makes no judgment about whether a major-version jump is safe. That's your responsibility as the developer. A is wrong because Gradle has no built-in awareness of Semantic Versioning rules. It doesn't treat a major-version increment as a special case or a signal to block automatic upgrades. SemVer is a convention, not something Gradle enforces automatically. C is wrong because Gradle resolves conflicts to a single version — it does not load multiple versions in isolation. That kind of class-isolation is the job of module systems or OSGi containers, not standard Gradle resolution. D is wrong because Gradle never fails simply due to a version conflict between major lines. It resolves silently using the newest-wins rule unless you explicitly configure strict constraints or rejection rules to force a failure. A good study tip: remember that Gradle's default behavior is optimistic — it picks the highest version and moves on. If you want stricter control, you must opt in via strictly, reject, or component-selection rules.
  8. What can the resolver conclude from the versions alone?
    Answer: The artifacts have equal precedence because build metadata is ignored when ordering versions Whenever you see a Semantic Versioning question involving build metadata (the part after +), your first instinct should be to recall one specific rule from the SemVer 2.0.0 specification: build metadata MUST be ignored when determining version precedence. This means that 3.2.1+linux.5 and 3.2.1+windows.8 share identical precedence because their major, minor, and patch segments — 3, 2, and 1 — are exactly the same, and the build metadata simply does not participate in the comparison. That makes C the correct answer: the resolver cannot distinguish between them using SemVer ordering alone. Answer A is wrong because SemVer never sorts build metadata lexicographically to determine precedence. The idea that linux beats windows in "reverse order" has no basis in the specification whatsoever — it applies a made-up rule to a field that is already excluded from comparison. Answer B commits a similar mistake: comparing the numeric identifiers 8 and 5 within the build metadata fields. Even if 8 > 5 as integers, those values live in the build metadata section, which the spec explicitly says to ignore. Numeric comparison rules that apply to pre-release identifiers do not carry over to build metadata. Answer D is wrong because neither version contains a pre-release label (which would appear between the patch number and +, like 3.2.1-alpha+linux.5). The + delimiter introduces build metadata, not a pre-release marker. A reliable study tip: memorize the three SemVer delimiters — - signals pre-release, + signals build metadata — and remember that only pre-release identifiers affect precedence, never build metadata.
  9. Which action most directly makes clean builds reproducible while retaining the declared range?
    Answer: Commit a lockfile recording 1.6.2, and update it deliberately after testing newer releases When you see a question about build reproducibility, ask yourself: what mechanism locks in a specific resolved version without permanently narrowing the declared flexibility of your version range? A lockfile solves this precisely. When a build tool generates a lockfile, it records the exact version actually resolved — in this case 1.6.2 — and commits that snapshot to source control. Every subsequent clean build reads the lockfile and uses 1.6.2 deterministically, regardless of what new versions appear in the registry. Crucially, the original range >=1.4.0 <2.0.0 stays untouched in your build descriptor, so when you deliberately test 1.7.0 and decide it's safe, you update the lockfile to record it. This is exactly what A describes, making it the correct answer. B is wrong because widening the range to <3.0.0 does the opposite of restricting resolution — it introduces more potential versions, increasing non-determinism rather than eliminating it. C is wrong because version metadata or suffixes like +tested are not a standard Maven/Gradle mechanism for suppressing automatic resolution; the resolver won't skip a semantically valid version just because of an appended label. D is wrong because deleting local caches doesn't fix non-determinism — it actually guarantees the resolver hits the remote registry every time, making it more likely to pick up new releases like 1.7.0, not less. As a study tip: on dependency management questions, distinguish between range declarations (what you allow) and lockfiles (what you actually use). These are separate concerns, and mixing them up is a common trap.
  10. Which version can resolve the conflict without changing either requirement?
    Answer: 1.8.6, because it lies within the intersection of the two version ranges When resolving dependency conflicts, your job is to find the intersection of all version ranges — the set of versions that simultaneously satisfies every module's requirement. Think of each range as a constraint, and a valid resolution must pass all constraints at once. Here, module A requires util >=1.6.0 <2.0.0 and module B requires util >=1.8.0 <1.9.0. To find the intersection, take the stricter lower bound (>=1.8.0) and the stricter upper bound (<1.9.0). The valid range becomes >=1.8.0 <1.9.0. From the available versions, only 1.8.6 falls inside this window — making B the correct answer. Choice A is wrong because 1.7.9 satisfies A's range but fails B's requirement of >=1.8.0. Satisfying only the broader range isn't enough; every constraint must be met simultaneously. Choice C is wrong because 1.9.0 violates B's strict upper bound <1.9.0. The < operator means strictly less than, so 1.9.0 is excluded — a very common off-by-one mistake to watch out for. Choice D is wrong because 2.0.0 violates A's upper bound <2.0.0, and major version numbers carry no special override power in semantic versioning — a version doesn't "supersede" range restrictions just because it's newer. Study tip: Whenever you see overlapping version ranges, immediately calculate the intersection by taking the highest lower bound and the lowest upper bound. Then check which available versions land inside that window — any version outside it disqualifies itself, regardless of which individual module accepts it.

Start this quiz