Real‑world examples of common dependency resolution failures explained
Before definitions, let’s start where developers actually feel pain: the build breaks. Here are several real examples of common dependency resolution failures explained in the context of everyday tools:
- A React app using
npmsuddenly refuses to install after a minor update because two libraries demand incompatible versions ofreact-dom. - A Python data pipeline on
pip/poetrycan’t resolvepandasbecause a transitive dependency quietly dropped support for your Python version. - A Java microservice using Maven fails its CI build when a new transitive dependency requires Java 17 but your org is still on Java 11.
- A Rust project using Cargo breaks because two crates require mutually exclusive feature flags of the same underlying crate.
- A Dockerized Node service fails on
npm ciin CI, even though it works locally, because the lockfile was generated on macOS and production runs on Linux with different native binaries. - A security scan blocks deployment because the resolver wants to upgrade a vulnerable library, but your direct dependency pins an older, incompatible version.
These are the kinds of real examples we’ll unpack in detail. Each section below gives examples of common dependency resolution failures explained with typical error messages, why they happen, and pragmatic fixes.
Version conflict: the classic dependency tug‑of‑war
One of the best examples of common dependency resolution failures explained in almost every ecosystem is the simple version conflict. Two libraries both depend on the same package, but they require versions that cannot be satisfied together.
Imagine a Node backend using npm:
- Your app directly depends on
express@4.18.0. - You also depend on
some-auth-lib@2.0.0, which in turn depends onexpress@^5.0.0.
When you run npm install, you get a message along the lines of:
ERESOLVE unable to resolve dependency tree
Found: express@4.18.0
node_modules/express
Could not resolve dependency:
peer express@"^5.0.0" from some-auth-lib@2.0.0
This is a textbook example of a common dependency resolution failure explained by mutually exclusive version ranges. The resolver can’t pick a single express version that satisfies both your app and the auth library.
Typical fixes include:
- Upgrading your app to be compatible with
express@5. - Downgrading
some-auth-libto a version that still supportsexpress@4. - In some ecosystems (like npm with legacy behavior), letting the resolver install multiple versions side by side. In modern strict modes (
npm@7+,pnpm,yarn berry), the resolver is intentionally stricter to prevent runtime surprises.
You’ll see the same pattern in Maven (Failed to collect dependencies), Gradle (Could not resolve all files), and pip/Poetry (ResolutionImpossible). The underlying story is identical: incompatible version requirements for the same package.
Transitive dependency breakage after a “harmless” update
Another example of common dependency resolution failures explained in real teams: you bump one top‑level dependency by a patch version and suddenly a completely different package fails to resolve.
Consider a Python project using poetry:
- You upgrade
requestsfrom2.31.0to2.32.0. requestsupdates its dependency oncharset-normalizerto a newer major version.- That newer
charset-normalizerno longer supports Python 3.8, but your production still runs 3.8.
When you run poetry update, you might see:
Because project depends on charset-normalizer (>=3.0.0) which requires Python >=3.9,
version solving failed.
Nothing in the error message mentions requests, but that subtle transitive change is the root cause. This is a real example of how transitive dependencies can break compatibility even when you only touched a “safe‑looking” version.
Practical guardrails:
- Use lockfiles (
poetry.lock,Pipfile.lock,package-lock.json,Cargo.lock) and treat them as versioned artifacts. - In CI, use
install --frozen-lockfileor equivalent to ensure the same resolved graph is used everywhere. - When updating, prefer smaller, explicit updates and review dependency change logs. Tools like
pip-auditandnpm outdatedcan help you see what’s changing.
The Python Packaging User Guide from the Python Software Foundation is a solid reference for dependency management best practices.
Platform and architecture mismatches (works on my machine, fails in CI)
A particularly frustrating example of common dependency resolution failures explained by environment drift is when everything works locally but breaks in Docker or CI.
Picture a Node service that uses a native module like bcrypt or node-sass:
- You run
npm installon macOS with Apple Silicon. - The installer downloads prebuilt binaries for that architecture.
- You commit the lockfile.
- In CI, you build on Linux x86_64. The resolver tries to reuse the same version and integrity metadata, but the platform‑specific binary is incompatible or missing.
The error might look like:
Error: Failed to load native module 'bcrypt'.
Module did not self-register.
or during install:
npm ERR! notsup Unsupported platform for bcrypt@x.y.z: wanted {"os":"darwin"} (current: {"os":"linux"})
This is a classic example of a dependency resolution failure explained by OS/architecture constraints. The metadata on the package declares platform restrictions, and the resolver correctly refuses to install it on an incompatible environment.
Mitigations:
- Run installs inside the same container image you use for deployment, so the resolver sees the real platform.
- Avoid committing
node_modules; rely on lockfiles and reproducible installs instead. - Prefer pure‑JS or cross‑platform alternatives where performance allows.
You’ll see similar behavior in Rust (Cargo choosing the wrong target), Go modules with GOOS/GOARCH, and Java projects that rely on JNI libraries.
Incompatible language or runtime versions
Another best example of common dependency resolution failures explained in modern stacks is language version drift. Libraries move on faster than organizations.
Take a Java project using Maven in 2025:
- Your org standard is Java 11.
- You add a new library that requires Java 17 (
<maven.compiler.target>17</maven.compiler.target>in its POM or uses Java 17 features in bytecode). - Maven resolves dependencies fine, but when you run tests in CI with Java 11, you see:
java.lang.UnsupportedClassVersionError:
com/example/NewLib has been compiled by a more recent version of the Java Runtime
(class file version 61.0), this version of the Java Runtime only recognizes up to 55.0
In some cases, the resolver itself will warn that no compatible variant is available if the library uses version‑specific artifacts (for example, classifiers for different Java versions). This is an example of dependency resolution failure explained by runtime incompatibility rather than missing packages.
You’ll see similar patterns in:
- Python libraries dropping support for 3.7/3.8 while your environment is still on them.
- Node packages requiring
node >= 18while your production image is pinned to 16. - .NET packages built for
net8.0when your app targetsnet6.0.
The fix is always a negotiation between upgrading the runtime, choosing older library versions, or switching to an alternative library that still supports your current platform. The OpenJDK documentation and vendor docs from Microsoft or Oracle are good references for runtime support timelines.
Security‑driven resolution failures in 2024–2025
In 2024–2025, security scanners and supply‑chain tooling are part of most pipelines. That creates a newer class of examples of common dependency resolution failures explained by security policy rather than purely technical incompatibility.
Imagine a Node service with these constraints:
- Your
package.jsonpinslog4js@2.15.0. - Your organization’s security scanner (SCA) flags that version as high‑severity due to a CVE.
- The scanner or policy engine forces
log4jsto be upgraded to^3.0.0. ^3.0.0drops support for Node 14, but your base image still runs Node 14.
The resolver now has conflicting constraints:
- Security policy:
log4js >= 3.0.0. - Runtime policy:
node 14, butlog4js >= 3.0.0requiresnode >= 16.
Depending on the tooling (GitHub Dependabot, Snyk, internal SCA), you might see a failed PR, a blocked merge, or a failed npm install with an engine mismatch.
This is a modern example of a dependency resolution failure explained by external policies. The actual code might “work,” but the resolver refuses to produce a graph that violates security or engine requirements.
Practical steps:
- Treat SCA findings like any other constraint in your dependency graph.
- Coordinate runtime upgrades with library upgrades instead of treating them as separate projects.
- Use advisory databases (for example, NVD at NIST) to understand which versions are actually affected.
Lockfile drift and partial updates
Lockfiles solve a lot of problems, but they introduce their own class of examples of common dependency resolution failures explained by drift.
Consider a monorepo using Yarn or pnpm:
- Team A updates
service-a/package.jsonand runsyarn install, which updates the root lockfile. - Team B has an old branch with a different
package.jsonsnapshot. - When Team B rebases, the lockfile and manifest are out of sync.
- CI runs
yarn install --frozen-lockfileand errors:
error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`.
Nothing is inherently wrong with the packages; the resolver is just refusing to reconcile conflicting instructions. This is a subtle example of a dependency resolution failure explained by version control practices.
Mitigations:
- Encourage smaller, focused dependency changes and fast merges.
- Regenerate lockfiles in a clean environment when conflicts appear, and review diffs carefully.
- In large monorepos, consider workspace‑aware tools (pnpm workspaces, Yarn workspaces, Nx, Bazel) that handle partial updates more predictably.
You’ll see analogous behavior in Python (poetry.lock vs pyproject.toml) and Rust (Cargo.lock vs Cargo.toml) when files get out of sync.
Native system libraries and headers missing
Some of the most confusing examples of common dependency resolution failures explained on Linux or macOS involve native compilation. The package manager can find the package, but it can’t build it because system‑level dependencies are missing.
For instance, a Python project using pip on a fresh Ubuntu server:
- You install
psycopg2to talk to PostgreSQL. pipdownloads the source distribution because there’s no prebuilt wheel for your exact Python/platform combo.- The build fails with:
Error: pg_config executable not found.
or
fatal error: libpq-fe.h: No such file or directory
From the resolver’s perspective, this is a dependency resolution failure explained by missing system libraries (libpq-dev, PostgreSQL client headers). The Python packaging toolchain expects those to be present.
Common patterns:
- C/C++ build tools missing (
build-essential,clang,Xcode Command Line Tools). - SSL libraries missing (
libssl-dev), causing failures when building crypto‑related packages. - GPU libraries missing or mismatched versions for ML frameworks.
The fix usually involves installing OS‑level dependencies with apt, yum, brew, or similar, then rerunning the installer. The Python Packaging User Guide and distro docs often list required system packages for popular libraries.
Real examples of debugging strategies that actually work
Seeing examples of common dependency resolution failures explained is only half the story. The other half is how to debug them quickly instead of randomly bumping versions until something sticks.
Patterns that help across ecosystems:
- Read the entire error message. Resolvers often tell you exactly which constraints conflict. Look for phrases like “requires,” “but found,” “because,” and “version solving failed.”
- Reproduce in a clean environment. Use a fresh container or virtualenv to rule out local cache issues.
- Inspect the resolved graph. Tools like
npm ls,pipdeptree,mvn dependency:tree, andcargo treeshow who depends on what. - Pin or relax versions strategically. Over‑pinning can cause conflicts; over‑relaxing (
*orlatest) invites surprises. Aim for ranges that reflect real compatibility. - Align runtime, tooling, and policy. Make sure your language version, package manager version, and security policies are consistent across dev, CI, and prod.
For large organizations, adopting internal guidelines similar in spirit to the U.S. government’s secure software development guidance can help keep dependency practices consistent across teams.
FAQ: short answers with concrete examples
What are some real examples of common dependency resolution failures?
Real examples include npm refusing to install due to incompatible peer dependencies, pip reporting ResolutionImpossible when two packages require conflicting versions of numpy, Maven failing because a transitive dependency requires a newer Java version than your runtime, and Cargo failing to pick a crate version when feature flags conflict.
Can you give an example of a dependency conflict caused by security updates?
A typical example of a dependency conflict is when a security scanner forces an upgrade of a vulnerable library to a version that no longer supports your current runtime (for instance, upgrading a Node library that drops support for Node 14), while your project is pinned to that older runtime. The resolver can’t satisfy both the security requirement and the runtime constraint.
How can I avoid these examples of common dependency resolution failures in CI?
Use lockfiles, run installs with --frozen-lockfile or equivalent, keep your CI images’ language runtimes in sync with local development, and automate regular, small dependency updates instead of big yearly upgrades. Also, document which language versions and package manager versions are supported across your organization.
Are there tools that help explain why resolution failed?
Yes. pip with the new resolver, poetry, npm@7+, Yarn Berry, Maven, Gradle, and Cargo all provide increasingly detailed error messages. Many also support verbose flags (-v, --debug) and graph inspection commands. External tools like pipdeptree, npm ls, and mvn dependency:tree give you a visual of the dependency graph so you can see exactly where conflicts arise.
Is it better to pin exact versions or use version ranges?
Neither extreme works well. Pinning everything to exact versions reduces surprises but increases the odds of hard conflicts when you add new packages. Very loose ranges invite unexpected breakage. A balanced approach is to pin direct dependencies to known‑good versions, allow reasonable ranges for transitive dependencies, and rely on lockfiles to capture the exact resolved graph.
Related Topics
Explore More Dependency Errors
Discover more examples and insights in this category.
View All Dependency Errors