Metadata Card
- Prerequisites: Basic terminal operations (Ch2)
- Estimated time: 35 min
- Core difficulty:
- Completion marker: Can install/update/remove dependencies with a package manager, understand the basic role of build tools
Your Progress
You're still in the workshop before setting out. The workshop's tool wall is gradually filling up — terminal, Git, debugger all in their places. But you glance at your neighbor's old project workbench: their code references a bunch of ready-made toolkits, while you have to write everything from scratch.
You've learned to use the terminal and Git in the workshop. But you quickly realize: a modern project almost never reinvents the wheel. You want to use someone else's logging library, JSON parser, testing framework — but where are they? How do you get them? And after you get them, how do you use them in your project? That's what this stop is all about.
Your Task
You wrote a small tool and want to add JSON parsing. You're not planning to write your own parser — that'd be thousands of lines of code and a lot of time. You've heard someone online already wrote one, and it performs well. But you need to know: where to find it, how to get it, how to make your code find it, and — if the library gets updated one day — how to upgrade. This is what package managers and build tools do: they connect your code to thousands of open-source libraries worldwide, handling versions, dependencies, and the entire build chain.
Chapter Layers
- Required reading: Why you shouldn't copy-paste someone else's code, how to install a dependency with a package manager, the purpose of a lock file, basic meaning of semantic versioning (MAJOR.MINOR.PATCH)
- Optional reading: Transitive dependencies and dependency conflicts, Maven's "nearest wins" strategy, Gradle DSL configuration syntax,
mvn dependency:treeto view the dependency tree- Advanced: Setting up a private package repository (Nexus/Artifactory), monorepo management tools
This chapter will NOT require you to understand
- Maven's dependency mediation details (nearest wins vs first declaration) — look it up when conflicts occur
- Gradle's Groovy/Kotlin DSL complete syntax
- Setting up and maintaining a private repository
The Breakthrough · Tracing the Origins
Scenario: You Need a JSON Library
Suppose you're writing a Java project. You need to parse a JSON string to read in user info. Your first instinct is to write it yourself?
// Don't do this — writing your own JSON parser is a waste of time
public class MyJsonParser {
// You'd need to handle nested quotes, escape characters, Unicode...
// At least five hundred lines of code, plus tests
}Of course you're not going to write it yourself. You know there's a library called Jackson that does exactly this. But the problem is — how do you get Jackson? Download a jar and throw it into the project folder? Manually managing dozens of jar versions will eventually confuse you.
First Move: Package Manager
A package manager is your "supply station manager." You tell it "I need Jackson," and it:
- Downloads the correct version from a remote repository
- Downloads all the dependencies of dependencies (transitive dependencies) along with it
- Places them where your project can find them
- If you want to upgrade — one command
The workshop master's tools have different "dialect" zones. Each programming language ecosystem has its own package manager — different names, but same job. Let's start with the three most common ones. You'll see they look different, but the soul is the same.
Python: pip
You walk up to the Python workbench. The rule here is simple: whatever package you need, use pip install. To make HTTP requests, you use the requests library — no need to write socket code yourself. First, check if the workbench has pip:
# First check if pip is available
pip --version
# Install a library
pip install requests
# Install a specific version
pip install requests==2.31.0
# See what's installed
pip listAfter installation, your Python code can import it directly:
import requests
response = requests.get("https://api.github.com/users/octocat")
data = response.json() # requests automatically parses the JSON for you
print(data["login"]) # Output: octocatThat simple. Install it and use it — no need to worry about what other libraries this one depends on.
You move from the Python workbench to the Node.js workbench. The materials used here are different — JavaScript libraries live in the npm registry. Think of it as another supply station, just with a different record-keeping language.
Node.js: npm
There's a sign at the front: Node Package Manager, abbreviated npm. How you use it is slightly different — first initialize a project manifest, then install things:
# Initialize a new project (generates package.json)
npm init
# Install a library (lodash is a utility library)
npm install lodash
# After installation, your project will have a node_modules/ folder
# package.json will also have a dependencies fieldAfter installing, you summon lodash in your code like this. Notice — JavaScript uses require (or the more modern import) to bring in a library:
const _ = require('lodash');
const numbers = [4, 2, 8, 6];
const sorted = _.sortBy(numbers);
console.log(sorted); // [2, 4, 6, 8]After installation, you notice a new file called package.json in your project. The workshop master picks it up and says: "This is your supply manifest. Whoever gets your project, just look at this file to know what to install."
package.json is npm's core configuration file. It's like a supply checklist, recording what external materials your project needs:
{
"name": "my-tool",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.21",
"express": "^4.18.2"
}
}See ^4.17.21? The ^ is a version range symbol, meaning "install the latest 4.x.x version, but don't jump to 5.0.0." This is the package manager's core capability — version management. Without it, your project's dependencies would become a tangled mess within a week.
You walk from the Node.js workbench to the Java workbench. The tools here are heavier — Java's package manager is called Maven, and it doesn't just install packages — it also compiles and packages. The workshop master hands you a piece of paper: "Java's supply manifest isn't JSON, it's XML."
Java: Maven
Java has its own package manager — more accurately called a build tool + package manager: Maven.
Maven uses pom.xml to manage dependencies:
<project>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
</project>Then execute in the command line:
mvn compileMaven automatically goes to Maven Central (the world's largest Java package repository) and downloads Jackson and all the libraries it depends on. After installation, you write code in your editor, and Jackson's classes are already waiting in your classpath — like a supply station manager delivering materials right to your workbench, within arm's reach.
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"name\":\"Alice\",\"age\":30}";
// Convert JSON string into a Java object
// (Assume there's a User class with name and age fields)
User user = mapper.readValue(json, User.class);
System.out.println(user.getName()); // Alice
}
}See the difference? pip is a one-line command, npm is a one-line command, Maven is configuring an XML file — but their essence is the same: declare your dependencies, the tool handles the rest.
Gradle: Another Option
Advanced: Gradle is widely used for Android and modern Java projects, but its DSL syntax is not the main thread of this chapter. Just know it exists after reading.
You just learned Maven's XML, but the Java world has another tool — more concise, more popular in modern Java and Android projects. The workshop master pulls out a shorter list: "Some people think XML is too long and want to write less."
Gradle is a competitor to Maven, popular in Android development and modern Java projects. It uses Groovy or Kotlin DSL instead of XML:
// build.gradle
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}The command is similar:
gradle buildGradle's core philosophy is the same as Maven's — just a more concise configuration and, in some scenarios, faster builds.
Second Move: Transitive Dependencies
A library almost always depends on other libraries. This is called transitive dependencies. For example:
You use Jackson
└── Jackson depends on Jackson-core
└── Jackson-core doesn't depend on anything elseThe package manager handles this chain automatically. You just declare you want Jackson, and it downloads Jackson-core for you too.
Advanced: The following content about transitive dependencies and version conflicts is something you'll almost never encounter when you're installing just one library. Come back when your project is big enough to have a dozen dependencies — or when you first encounter a
ClassNotFoundException.
But what if two of your dependencies each need a different version of Jackson-core?
Your project
├── Lib A (needs Jackson-core 2.14)
└── Lib B (needs Jackson-core 2.15)This is a dependency conflict. Package managers handle it differently:
- Maven: Uses "nearest wins" strategy — picks the version with the shortest path in the dependency tree
- Gradle: Picks the highest version by default, but you can force a specific version in the configuration
- npm/pip: Install independent copies for each dependency (different versions can coexist)
Here's a real Maven conflict scenario:
# Use mvn dependency:tree to view the dependency tree
mvn dependency:treeOutput:
[INFO] com.example:my-app:jar:1.0-SNAPSHOT
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.15.2:compile
[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.15.2:compile
[INFO] \- com.example:lib-b:jar:1.0:compile
[INFO] \- com.fasterxml.jackson.core:jackson-core:jar:2.14.0:compileSee the problem? Jackson-core appears in two versions. Maven picks 2.15.2 (shorter path), 2.14.0 is ignored. This usually works fine — 2.15 is backward-compatible with 2.14 — but not always.
Third Move: Semantic Versioning (SemVer)
Why can package managers handle versions automatically? Because the open-source community follows a convention — Semantic Versioning (SemVer).
Version number MAJOR.MINOR.PATCH, like 2.15.2:
- MAJOR increases (1.0.0 → 2.0.0): Breaking API changes. Your old code might not work.
- MINOR increases (2.14.0 → 2.15.0): New features added, but backward-compatible. Old code still works.
- PATCH increases (2.15.1 → 2.15.2): Bug fixes, backward-compatible. Safe to upgrade.
Version range symbols let you precisely control "what can automatically upgrade":
| Notation | Meaning |
|---|---|
^2.15.2 | Can upgrade within 2.x.x (won't jump to 3.0.0) |
~2.15.2 | Can upgrade within 2.15.x (won't jump to 2.16.0) |
>=2.14 <3.0 | Greater than or equal to 2.14, less than 3.0 |
2.15.2 | Locked to this version exactly |
This is the "ID card" of a package. When releasing a new version, developers update the corresponding number based on the type of change. The package manager decides whether to install the new version based on the range you configured.
** Try it: Add a Logging Library to Your Project**
You've learned how to install packages. But you're still debugging with System.out.println one line at a time. After debugging, you need to go back and delete them — and if you miss one, your production console will be full of "made it here" messages.
"Isn't there a more dignified way than printing?" you ask. "I want a way to install a 'black box' in my program — log key events, format error messages nicely, and filter by level."
Let's do something real — add a logging library to your project, instead of writing System.out.println everywhere.
The workshop master hands you three boxes, each containing a logging library for a different language. "Pick the one you use," he says. "The usage is the same — log key events, format error messages nicely, filter by level. Much more professional than typing print statements in the terminal."
Python version:
# Install loguru — a pleasant logging library
pip install logurufrom loguru import logger
logger.info("Program started")
logger.debug("Current user ID: {}", user_id)
logger.error("Database connection failed: {}", error_message)Node version:
npm install winstonconst winston = require('winston');
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
]
});
logger.info('Program started');
logger.error('Database connection failed');Java version (Maven):
Add to pom.xml:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>Then mvn compile, and Maven downloads them. In code:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.info("Program started");
log.error("Database connection failed");
}
}Try installing — see how much nicer the output looks? Timestamps, log levels, source locations, all automatically added for you. This is the result of the package manager "inviting" the library into your project.
Common Pitfalls
Story: The Person Who Deleted node_modules
There's a widely circulated joke: the everyday task of a frontend developer is "delete node_modules and reinstall."
Imagine a real Node.js project. After installing a few packages, the node_modules folder might contain hundreds or thousands of subfolders. Why? Because each package has its own dependencies, expanded in a chain.
# Check how big node_modules is
du -sh node_modules/
# Output: 285M node_modules/A small project, and the expanded dependency chain is 285MB. Delete and reinstall?
rm -rf node_modules/
npm installThis seems like a joke, but behind it is a serious issue: version locking.
If your package.json says "lodash": "^4.17.21", installing today versus next week might give you different versions (if 4.17.22 is released). If 4.17.22 introduces a bug, your automated build inexplicably breaks.
Solution: Lock File
npm→package-lock.jsonpip→requirements.txtorPipfile.lockMaven→pom.xmlenforces specific versions
The lock file records the exact version of every library you actually installed and their dependency tree. That way, everyone on the team, the automated build, and the production environment all install the same thing.
# Use the lock file to ensure consistency
npm ci # Install exactly according to lock file, don't update
npm install # Install according to package.json, may update lock fileThe difference between npm ci and npm install is: the former strictly follows the lock file, suitable for automated builds and production deployment; the latter may update the lock file, suitable for development when adding new dependencies.
Another Trap: Global Installation vs Local Installation
# Global installation (like buying a shared printer for the workshop)
npm install -g typescript
# Local installation (for just the current project)
npm install typescriptGlobally installed tools can be used directly from the command line (tsc), but different projects might need different versions. Local installation keeps each project self-contained. Best practice: development tools (TypeScript, ESLint) installed locally, invoked with npx; CLI tools (create-react-app, vue-cli) can be installed globally as needed or used with npx.
Final Challenge
Warm-up (5 min, required): Open terminal, run
pip listto see how many Python libraries you have. Then runnpm list -g --depth=0to see global Node packages. Count them.Challenge (30 min, optional): Create a new Java Maven project (use
mvn archetype:generate), add Jackson dependency topom.xml. Write code to read a JSON file and print it out. Then usemvn dependency:treeto observe the dependency tree.Observe: Deliberately write two different versions of the same dependency in
pom.xml, usemvn dependency:treeto see how Maven handles the conflict.Troubleshooting: Your colleague sent you a project. When you
npm install, you getModule not found. Checkpackage-lock.jsonto confirm versions. The lock file might be corrupted — try deletingnode_modulesandpackage-lock.json, then re-runnpm install.
Checklist
After this chapter, you should be able to:
- Explain the core problem package managers solve
- Install a library in each of three languages (Python/pip, Node/npm, Java/Maven)
- Read and understand semantic version numbers (
MAJOR.MINOR.PATCH) - Use
mvn dependency:treeor similar commands to view dependency trees - Understand the purpose of lock files and know when to commit them
Common Sticking Points
- pip reports
Permission denied: Don't usesudo pip install. Usepip install --useror create a virtual environment (python -m venv venv) - npm install hangs: Network issue. Switch to a domestic mirror:
npm config set registry https://registry.npmmirror.com - Maven downloads too slow: Same issue. Configure Aliyun mirror in
~/.m2/settings.xml - "Jar conflict" error: Most common in Java projects. Use
mvn dependency:treeto see who's pulling in the conflicting version, use<exclusions>to exclude it - Should package.json and package-lock.json be committed to Git?: Yes, both.
package.jsonis the declaration,package-lock.jsonis the exact snapshot. Only things likenode_modules/and__pycache__/should go in.gitignore
No Need to Understand Now
- Build tool plugin mechanisms (Maven plugins / Gradle tasks): Default config is enough for now, plugins are advanced
- Private package repositories (Nexus / Artifactory): Company intranets need their own repositories; for now, use Maven Central / PyPI / npmjs.org
- Monorepo management tools (Lerna / Nx / Turborepo): Only needed when you manage dozens of packages as a team
- Python's pipenv and poetry: Virtual environment and dependency management alternatives — worth knowing but outside this chapter
- Dependency injection frameworks (Spring / Guice): That's another level of abstraction, much more complex than your current needs
Traveler's Notes
A package manager is your supply station manager: tell it what you need, it goes to the warehouse, sorts out the versions, straightens out the dependency chain. You never have to manually download jar files, copy-paste code, or — write your own JSON parser.
→ Preview of Next Stop
Dependencies installed, code written. Then the program crashes — the console turns red, full-screen English letters you can't understand a word of. Don't panic. Next stop, we learn to read errors, check logs, write minimal reproductions — every engineer's wilderness survival skill.