Java • Getting Started and Tooling

Semantic Versioning & Conflicts — Understand semantic versioning and dependency conflicts at a high level (intro)

Learn the three-number system that keeps Java libraries compatible and how conflicts arise when versions collide.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 6 figures

SECTION 1

Historical Context & Motivation

In the early days of software development, programmers wrote almost everything from scratch. As projects grew larger, developers started sharing reusable code in the form of libraries — pre-written packages of code that solved common problems. However, a major headache quickly emerged: when a library was updated, existing programs that depended on it would sometimes break. There was no universal system for communicating what had changed between one release and the next.

Imagine your favorite mobile app updating overnight, and suddenly all your saved settings are gone. That frustration is exactly what developers experienced with library updates. They needed a shared language to describe how much a library had changed so they could decide whether it was safe to upgrade. This need led to the creation of semantic versioning, a convention that encodes meaning directly into version numbers.

1990s • Ad-Hoc Versioning

Early open-source projects use inconsistent version numbering. Some use dates, some use arbitrary numbers, and there is no shared convention across the industry.

2004 • Maven Enters the Java World

Apache Maven introduces a build tool for Java that manages library dependencies automatically. Version numbers suddenly matter a lot more because Maven must decide which version to download.

2009–2011 • SemVer Specification Created

Tom Preston-Werner, co-founder of GitHub, publishes the Semantic Versioning specification (SemVer). It formalizes the MAJOR.MINOR.PATCH format that the community informally used.

2013–Present • Widespread Adoption

Build tools like Gradle for Java, npm for JavaScript, and pip for Python adopt SemVer as the expected versioning scheme. Dependency conflict resolution becomes a core feature of modern tooling.

The central question that semantic versioning answers is: If I upgrade this library, will my code still compile and run correctly? Understanding how to read a version number — and what can go wrong when two libraries need different versions of the same dependency — is a critical skill for any Java developer.

SECTION 2

Core Principles & Definitions

Semantic versioning assigns three numbers to every release of a library, written in the format MAJOR.MINOR.PATCH. Each number carries specific meaning about the type and severity of changes. A dependency is any external library your project relies on, and a dependency conflict occurs when two parts of your project require different, incompatible versions of the same library.

1

MAJOR Version

Incremented when changes break backward compatibility. Code written for version 1.x may not work with version 2.x. Example: 1.4.2 → 2.0.0
2

MINOR Version

Incremented when new features are added without breaking existing code. Everything that worked before still works. Example: 2.3.1 → 2.4.0
3

PATCH Version

Incremented for bug fixes that don't add new features or break anything. Safe to update almost always. Example: 2.4.0 → 2.4.1
4

Backward Compatibility

The principle that newer versions should still support old usage patterns. When a MINOR or PATCH update is released, your existing code should not break.
5

Dependency Conflict

Occurs when Library A requires commons-io 2.6 but Library B requires commons-io 2.11. Your build tool must choose one, and the wrong pick can cause runtime errors.
✦

KEY TAKEAWAY

Think of a version number like a food label. The MAJOR number is like a complete recipe change — the dish might taste totally different. The MINOR number is like adding a new side dish — everything you liked is still on the menu. The PATCH number is like fixing a typo on the menu — the food itself hasn't changed at all.

SECTION 3

Visual Explanation — Anatomy of a Version Number

Anatomy of a Semantic Version2.14.3MAJORBreaking changesOld code may failMINORNew features addedBackward compatiblePATCHBug fixes onlySafe to updateRisk Level of UpgradingHIGH — Major bumpMEDIUM — Minor bumpLOW — Patch bumpExample progression: 2.14.3 → 2.14.4 (patch) → 2.15.0 (minor) → 3.0.0 (major)Each dot-separated number resets rightward: 2.15.0 resets PATCH to 0; 3.0.0 resets both MINOR and PATCH.
The diagram above breaks down the version string 2.14.3 into its three components. Notice that when the MINOR version increments, the PATCH resets to zero, and when the MAJOR version increments, both MINOR and PATCH reset to zero.

When you look at a version like 2.14.3, you can immediately read it as: the second major generation of this library, the fourteenth set of new features within that generation, and the third bug-fix release since the last feature update. This structure gives developers a quick snapshot of how safe it is to upgrade. A jump from 2.14.3 to 2.14.4 should be painless, while a jump to 3.0.0 signals that you should carefully review the migration guide before updating.

SECTION 4

How Dependency Resolution Works

In a Java project, you typically use a build tool like Maven or Gradle to manage dependencies. When you declare a dependency in your project file, the build tool downloads the correct version from a repository (like Maven Central). But here is where things get interesting: your dependencies can have their own dependencies — these are called transitive dependencies.

Version Range Notation

Build tools allow libraries to specify flexible version ranges rather than pinning an exact version. In Maven, you might see notation like [2.0, 3.0), which means "any version from 2.0 up to but not including 3.0." The square bracket means inclusive and the parenthesis means exclusive. In Gradle, the equivalent would be 2.+ which means "any version starting with 2." These ranges give the build tool flexibility to find a version that satisfies everyone.

VERSION RANGE NOTATION

[2.0,3.0)=v∣2.0.0≤v<3.0.0[2.0, 3.0) = { v | 2.0.0 ≤ v < 3.0.0 }

[ ] = inclusive boundary, ( ) = exclusive boundary. v = any version that falls within the range.

The Diamond Dependency Problem

The most common conflict scenario is the diamond dependency problem. Your project depends on Library A and Library B. Both A and B depend on Library C, but they each need a different version. Since Java loads each class only once at runtime, there can only be one version of Library C on the classpath. The build tool must pick one, and if it picks wrong, you get errors like NoSuchMethodError or ClassNotFoundException at runtime.

ℹ

How Build Tools Resolve Conflicts

Maven uses the "nearest definition" strategy — it picks the version declared closest to your project in the dependency tree. Gradle, by default, picks the highest version it finds, assuming newer is better. Both strategies can fail, which is why understanding SemVer helps you make informed decisions when overriding versions manually.

SECTION 5

Types of Dependency Conflicts

Not all conflicts are created equal. Some are easily resolved by a build tool, while others require manual intervention. Understanding the types of conflicts helps you diagnose and fix them quickly.

Diamond Dependency ConflictYour ProjectLibrary A v1.2.0Library B v3.1.0requires v2.3requires v2.8Library C⚠ v2.3 or v2.8?✓ SAFE: Minor-only gapBoth are v2.x (same MAJOR)Upgrade to v2.8 — Library Ashould still work becauseMINOR changes are compatible✗ DANGER: Major gapIf A needed v1.9 and Bneeded v2.8, the MAJORversion differs — pickingeither could break the other
This diagram shows the classic diamond dependency shape. Your project depends on A and B, and both depend on Library C. The green box shows a safe scenario (same MAJOR version), while the red box shows a dangerous one (different MAJOR versions).
Conflict TypeDescriptionSeverity
Patch mismatchLibraries need different PATCH versions (e.g., 2.4.1 vs 2.4.3). Usually safe — just pick the higher patch.Low
Minor mismatchLibraries need different MINOR versions (e.g., 2.3.0 vs 2.8.0). Generally safe because MINOR changes add features without removing old ones.Medium
Major mismatchLibraries need different MAJOR versions (e.g., 1.9.0 vs 2.1.0). Dangerous — methods or classes may have been removed or renamed.High
Exclusion conflictA library explicitly excludes a transitive dependency that another library requires. Requires manual configuration to resolve.High

Common types of dependency conflicts ranked by severity

SECTION 6

Worked Example — Diagnosing a Version Conflict

Let's walk through a realistic scenario. You're building a Java project using Gradle, and your build file includes two libraries. After adding them, you get a mysterious NoSuchMethodError at runtime. Here's how to figure out what went wrong.

Resolving a Dependency Conflict in Gradle

  1. 1

    Step 1 — Identify the Error

    Your application compiles successfully, but when you run it, you see: java.lang.NoSuchMethodError: com.google.common.collect.ImmutableList.toImmutableList(). This error means the JVM loaded a version of the Guava library that does not contain the method toImmutableList().
    The problem is a version mismatch in the Guava library.
  2. 2

    Step 2 — Check the Dependency Tree

    Run gradle dependencies in your terminal. This prints a tree showing every library and its transitive dependencies. You discover that Library A pulls in guava:16.0 while Library B pulls in guava:31.1-jre. Gradle chose 16.0 because it was declared nearest to your project in the dependency tree (Maven nearest-definition behaviour), or your project explicitly pinned that version.
    Identified: Guava 16.0 vs 31.1-jre — this is a MAJOR version conflict, and the build tool resolved to the older 16.0.
  3. 3

    Step 3 — Analyze Using SemVer

    The method toImmutableList() was introduced in Guava 21.0. Because the build tool placed Guava 16.0 on the classpath — a version released before toImmutableList() existed — the JVM cannot find that method at runtime. Library B was compiled against Guava 31.1-jre, which does contain toImmutableList(), but the older version that was actually loaded does not. This is a classic case of the resolved version being too old to satisfy one of the libraries that depends on it.
    Library B relies on a method (toImmutableList()) that was not yet present in the resolved older version of Guava (16.0).
  4. 4

    Step 4 — Choose a Resolution Strategy

    You have three options. First, you can force a specific Guava version in your build file and hope both libraries work with it. Second, you can update Library A to a newer release that supports a more recent Guava version. Third, you can exclude Guava from Library A and manually add a compatible version that satisfies both. The best option is usually to update the older library so that all dependencies align.
    Solution: Update Library A to a version that works with a modern Guava release, or force Guava to a version both libraries support.

SECTION 7

Strengths & Limitations of Semantic Versioning

Semantic versioning is an incredibly useful convention, but it is not a silver bullet. It relies on library authors to follow the rules honestly, and it cannot capture every nuance of software change. Let's compare its strengths and limitations.

StrengthsLimitations
Provides a universal, human-readable signal about the nature of changes between releases.Authors can accidentally (or intentionally) break the rules, bumping MINOR when they should bump MAJOR.
Enables build tools to automatically resolve many version conflicts by understanding compatibility ranges.Behavioral changes (a method returns different results) may not trigger a MAJOR bump even though they break your code.
Makes it easy for developers to quickly assess upgrade risk just by glancing at the version number.The three-number system cannot express subtle information like performance regressions or deprecated-but-still-present methods.
Widely adopted across ecosystems (Java, JavaScript, Python, Rust), creating a shared vocabulary.Not all projects follow SemVer strictly. Some use calendar versioning (CalVer) or their own custom schemes.

Balancing the benefits and drawbacks of SemVer

✦

KEY TAKEAWAY

Think of SemVer like a traffic light system. PATCH = green light — go ahead and upgrade. MINOR = yellow light — proceed, but read the release notes. MAJOR = red light — stop and plan before upgrading. Just like a traffic light doesn't guarantee safety, SemVer doesn't guarantee compatibility — it signals intent.

SECTION 8

Connection to Advanced Tooling & Concepts

As you move beyond introductory Java projects, you'll encounter more sophisticated tools and strategies for dealing with versioning and conflicts. The concepts you've learned here form the foundation for understanding these advanced topics.

What You Learned (Intro)What Comes Next (Advanced)
Reading version numbers (MAJOR.MINOR.PATCH)Pre-release tags and build metadata (e.g., 2.0.0-beta.1+build.42)
Understanding the diamond dependency problemUsing Gradle's dependency constraints, BOMs (Bill of Materials), and platform dependencies to enforce versions
Build tool picks one version automaticallyCustom resolution strategies, dependency locking (lock files), and reproducible builds
Avoiding MAJOR version conflictsShading/relocating conflicting libraries (repackaging a library under a different package name)

How introductory concepts connect to advanced dependency management

In industry, large Java applications like those built with Spring Boot can have hundreds of transitive dependencies. Spring manages this complexity by publishing a curated set of compatible versions called a BOM. When you use Spring Boot's BOM, you don't need to specify versions for most libraries — the framework ensures everything plays nicely together. Understanding SemVer is the first step toward appreciating why tools like BOMs exist.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

A library updates from version 3.7.2 to version 3.8.0. According to semantic versioning rules, what kind of changes should you expect? Is your existing code likely to break?
Show answer
This is a MINOR version bump (7 → 8), with the PATCH resetting to 0. According to SemVer rules, a MINOR bump means new features have been added, but all existing functionality remains intact. Your existing code should not break because backward compatibility is preserved. However, it's still good practice to review the release notes, as behavioral changes can occasionally slip through even in MINOR releases.

Question 2: BASIC CALCULATION

A project's build.gradle specifies a dependency as implementation 'org.example:utils:2.+'. The repository contains versions 1.9.0, 2.0.0, 2.3.1, 2.5.0, and 3.0.0. Which version will Gradle select?
Show answer
The notation 2.+ means "any version that starts with 2." so it matches 2.0.0, 2.3.1, and 2.5.0. It does not match 1.9.0 (wrong MAJOR) or 3.0.0 (wrong MAJOR). Among the matching versions, Gradle picks the highest: 2.5.0. This range notation is useful because it automatically picks up MINOR and PATCH updates while preventing risky MAJOR jumps.

Question 3: INTERMEDIATE

Your project depends on library-X v1.4.0 and library-Y v2.1.0. Library X transitively depends on commons-io:2.6, and Library Y transitively depends on commons-io:2.11. Using your knowledge of SemVer, should Gradle's default strategy (pick the highest version) work safely here? Explain your reasoning.
Show answer
Both required versions of commons-io share the same MAJOR version (2), meaning they are intended to be backward compatible with each other. Version 2.11 should include everything that version 2.6 had, plus additional features and bug fixes. Therefore, Gradle's strategy of selecting the higher version (2.11) should work safely. Library X was written for 2.6, and since 2.11 is a MINOR update from 2.6, all the methods Library X uses should still exist. This is an example of a minor mismatch — the lowest severity conflict type.

Question 4: APPLIED

You are building a school project management app in Java. Your project uses a JSON parsing library at version 1.2.4 and a web framework that transitively pulls in the same JSON library at version 2.0.1. After building, your code compiles fine but crashes at runtime with NoSuchMethodError. Explain why this happens and describe two strategies you could use to fix it.
Show answer
This crash occurs because there is a MAJOR version conflict (1.x vs 2.x). The build tool selected one version (likely 2.0.1, the higher one), but your code was written against version 1.2.4. Because MAJOR version 2 may have removed or renamed methods that existed in version 1, your code calls a method that no longer exists — hence the NoSuchMethodError. Two fix strategies: (1) Update your own code to use the 2.x API — read the library's migration guide and change any method calls that were removed. (2) Force the build tool to use version 1.2.4 and check whether the web framework is compatible with 1.x; if not, you may need a newer version of the web framework that works with the 1.x JSON library.

Question 5: CRITICAL THINKING

A library author fixes a critical security vulnerability in their library. The fix requires removing a deprecated method that some users still call. The current version is 4.12.3. According to strict SemVer rules, what should the new version number be? Some argue that security fixes should always be PATCH-level updates to encourage fast adoption. Discuss both sides of this debate and take a position.
Show answer
Strictly following SemVer, removing a method is a breaking change, which requires a MAJOR bump — the new version should be 5.0.0. However, this creates a real dilemma. Users are less likely to upgrade to a new MAJOR version quickly because they fear breaking changes, yet the security fix is urgent. On the other hand, if the author releases it as 4.12.4 (a PATCH), users will auto-upgrade — but their builds may break unexpectedly, violating the trust that SemVer is supposed to provide. The best practice is to bump to 5.0.0 while simultaneously releasing 4.12.4 with the vulnerability patched without removing the deprecated method. This gives users both a safe immediate patch and a clear upgrade path.

SUMMARY

Lesson Summary

Summary

Semantic versioning uses the MAJOR.MINOR.PATCH format to communicate the nature of changes in a library release. A MAJOR bump signals breaking changes that may require code modifications. A MINOR bump adds new features while keeping existing code working. A PATCH bump fixes bugs without changing the API. When the MINOR version increases, PATCH resets to zero; when the MAJOR version increases, both MINOR and PATCH reset.

Dependency conflicts arise when two libraries in your project require different versions of the same transitive dependency. The classic pattern is the diamond dependency problem. Conflicts within the same MAJOR version are usually safe to resolve by picking the higher version, while conflicts across MAJOR versions may cause runtime errors like NoSuchMethodError. Build tools like Maven and Gradle automate resolution, but understanding SemVer helps you make smart decisions when automatic resolution fails.

Varsity Tutors • Java • Semantic Versioning & Conflicts