Libraries evolve. Semantic Versioning (SemVer) encodes compatibility promises in version numbers:
MAJOR . MINOR . PATCH
2 . 15 . 0
Change
Example
Meaning
Safe to Update?
PATCH
2.15.0 → 2.15.1
Bug fixes only
✅ Yes
MINOR
2.15.0 → 2.16.0
New features, backwards compatible
✅ Usually
MAJOR
2.15.0 → 3.0.0
Breaking changes
⚠️ Review needed
// Exact version — reproducible but requires manual updates implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' // Any 2.x version — automatic patches but risk of breakage implementation 'com.fasterxml.jackson.core:jackson-databind:2.+'
Berkeley builds on Unix, creates BSD license: "Do whatever you want, just credit us"
Maximum individual freedom
1983
Stallman sees companies closing BSD-derived source code. Launches GNU and GPL: "Share alike, forever"
Protect the commons
Both philosophies shaped the modern world:
BSD lineage (permissive)
GPL lineage (copyleft)
FreeBSD → macOS, iOS
GNU/Linux → Android, Cloud
Companies took the code and closed it — as intended
Companies must share kernel changes — as intended
The irony: BSD's freedom enabled companies to close the source — which is exactly what motivated Stallman to create a license that prevented it. Same code heritage, fundamentally different values.
Two Philosophies: Maximize Adoption vs. Protect the Commons
Adding a dependency is easy — one line in build.gradle. But every dependency is a commitment.
Before adopting a library or framework, ask yourself:
Who maintains this, and will they still be maintaining it in two years?
What happens if I need to switch away from it later?
Am I comfortable with the license terms?
How much will this shape the rest of my code?
The last question matters most: A utility library for string formatting has minimal impact — swap it out easily. A web framework influences your entire application structure. Migrating away could mean a rewrite.
Before adopting a library, investigate who's behind it and how active they are:
Indicator
What to Look For
Red Flags
Recent activity
Last commit? Last release?
No updates in 2+ years
Issue responsiveness
Are bugs acknowledged? PRs reviewed?
500 ignored issues
Bus factor
How many core maintainers?
Single maintainer
Documentation
Up-to-date? Comprehensive?
Outdated or missing docs
Sobering reality: OpenSSL — securing most of the internet — was maintained by a handful of volunteers until the Heartbleed vulnerability exposed how underfunded critical infrastructure can be.
Oracle acquired Sun (2009). Community worried about stewardship.
OpenOffice
LibreOffice
Oracle fired developers (2010). Community forked. LibreOffice won.
Terraform
OpenTofu
HashiCorp changed to restrictive license (2023). Linux Foundation hosts the fork.
The code can live on — but the disruption is painful for everyone. Forks are possible because the source is open. The only thing original creators keep is the trademark.
Behind every dependency in your build.gradle is a person — often unpaid, often alone.
The reality of OSS maintenance:
Most critical infrastructure runs on volunteer labor
OpenSSL (secures the internet): 2 maintainers pre-Heartbleed
Burnout is the #1 cause of project abandonment
L22's bus factor — at ecosystem scale:
Your team has 4 people. What if one leaves?
SnakeYAML has one maintainer. What if they leave?
The consequences ripple across thousands of projects
This isn't a team problem — it's an industry problem
The paradox: We trust billion-dollar systems to maintainers we've never met, often without paying them. The same HRT principles from L22 apply here — except the "team" is the entire open source community.
Is it safer to use a popular library or write your own? Almost always use the library — if it's well-maintained.
Why well-maintained libraries win:
Battle-tested by thousands of users
Security researchers scrutinize popular projects
Vulnerabilities get reported and patched
You benefit from specialists' expertise
When dependencies introduce risk:
Every dependency is an attack surface
Transitive dependencies multiply the risk
Unmaintained dependencies don't get patches
You're trusting code you've never read
The key insight: You're unlikely to out-engineer the Jackson team at JSON parsing, or the Spring Security team at authentication. But you DO need to keep your dependencies updated.
Supply Chain Attacks: When Dependencies Become Vectors
An attacker compromises a package deep in the dependency tree. Your app pulls it in transitively — you never knew it existed. Now the attacker's code runs in your application.
Incident
What Happened
Impact
left-pad (2016)
Developer unpublished an 11-line npm package
Thousands of builds broke — Facebook, Spotify, others
Log4Shell (2021)
Critical remote code execution in Log4j
Millions of apps affected; months to remediate
A chain is only as strong as its weakest link. The risks compound when you adopt a library and never update it, the library is abandoned, or a malicious actor compromises it.
Log4Shell: When a Logging Library Became an Attack Vector
In December 2021, a critical vulnerability in Log4j — a ubiquitous Java logging library — exposed millions of systems to remote code execution. This is the supply chain attack that changed how the industry thinks about dependencies.
The attack in plain English: Log4j had a "helpful" feature that let logged strings contain lookups like ${jndi:ldap://...}. If an attacker put this string anywhere your app logged — a username field, a search box, even a browser User-Agent header — Log4j would fetch and execute code from the attacker's server.
You wouldn't pass user input directly to dangerous operations. But logging doesn't feel dangerous.
Operation
Would You Pass User Input Directly?
Runtime.exec("rm " + userInput)
No way! User could type -rf / and delete everything
new File(userInput).delete()
No way! User could type ../../../etc/passwd
log.info("User searched for: " + userInput)
Sure, why not? It's just writing text to a file...
The trust assumption that broke: Every developer assumed log.info(userInput) was a sink — data goes in, gets written to a file, nothing else happens. Log4j made it a source of network requests and code execution. Logging became as dangerous as Runtime.exec() — but nobody knew.
You're not just a consumer of open source — you're a participant. L22's HRT principles apply to the global community, not just your team.
Apply L22's practices to OSS:
File good issues — you learned this in L22. Same template: steps to reproduce, expected vs. actual, environment.
Look for "good first issue" labels — many projects tag beginner-friendly tasks. This is your on-ramp.
Contribute docs, not just code — the most impactful first contribution is often a typo fix or a clearer example
Respect maintainer time — they owe you nothing. A polite, well-researched issue gets answered; "fix this!" gets ignored.
Apply L22's HRT:
Humility: You're new to their codebase. Ask before assuming.
Respect: Read the CONTRIBUTING.md before submitting a PR. Follow their conventions, not yours.
Trust: If a maintainer closes your issue, assume good faith. Ask why, don't demand.
Start now: Every one of you uses open source daily. You can contribute today — report a bug, improve docs, answer a question on Stack Overflow. You don't need permission to participate in the bazaar.
Why the Bazaar Works: Modularity All the Way Down
The bazaar model only works because of information hiding. Every successful OSS library is a module with a clean boundary.
You don't know — and shouldn't care — how Jackson tokenizes JSON internally. That's the whole point. Information hiding is what lets strangers' code snap together like LEGO bricks.
If your code violates information hiding, you can't swap components — even when better alternatives exist.
Leaky boundary — locked in:
// Scattered throughout your codebase JsonNode node =newObjectMapper() .readTree(input); // Cast to Jackson-specific types ObjectNode obj =(ObjectNode) node; obj.put("key","value"); // Use Jackson's streaming API directly JsonParser parser = factory.createParser(input); while(parser.nextToken()!=null){...}
Jackson internals leaked everywhere. Switching to Gson? Rewrite the whole app.
Clean boundary — swappable:
// One place defines the contract publicinterfaceJsonSerializer{ <T>StringtoJson(T object); <T>TfromJson(String json,Class<T> type); } // Jackson implementation (hidden behind boundary) classJacksonSerializerimplementsJsonSerializer{ privatefinalObjectMapper mapper = newObjectMapper(); // ... implementation details stay here }
Switch to Gson? Write one new class. Nothing else changes.
L18's boundary heuristics told you: isolate things that change independently. OSS libraries change on THEIR schedule, not yours. That's exactly where a boundary belongs.
Good boundaries don't just help you consume OSS — they're what make your code reusable in the first place.
What makes a module extractable into a reusable library?
Principle
What It Enables
Information hiding
Users don't depend on your internals — you can evolve freely
Clean interfaces
Users know exactly what to call and what to expect
Minimal dependencies
Users don't inherit YOUR dependency tree
Single responsibility
Users adopt only what they need — nothing extra
Every great OSS library started as a well-modularized internal component that someone realized others could use. Jackson, React, Kubernetes — all extracted from internal systems because the boundaries were clean enough to share.
The Virtuous Cycle: Modularity Enables the Ecosystem
Every principle we've studied this semester reinforces the others:
Information hiding lets strangers' code work together without understanding each other's internals
Clean boundaries let you swap one library for another when licenses change, maintainers disappear, or better options emerge
Modularity is what lets a single developer in Tokyo publish a component on Monday that teams worldwide adopt by Friday
Testability depends on all of the above — can you mock that dependency? Only if you injected it (L17) behind a clean interface (L18)
The bazaar doesn't work without modularity. And modularity doesn't matter without the bazaar to supply the components. They need each other. Every time you type implementation 'some:library:1.0', you're making an architectural decision. Evaluate it like one.