Licensing, SPDX Headers, and the Apache 2.0 Story
OpenSearch exists because of a license. Not a feature, not a performance number — a license. The entire project is a bet that a distributed search engine should be available under a permissive, OSI-approved license, and that bet is enforced mechanically in the build on every single PR. This chapter explains why OpenSearch is Apache License 2.0, the SPDX header that must sit atop every source file, the build-time license checks that gate merges and releases, and what would actually block a release on licensing grounds.
Licensing is not a side concern in OpenSearch. It is the founding concern.
Why OpenSearch Is Apache 2.0: The 2021 Fork
In January 2021, Elastic relicensed Elasticsearch and Kibana away from the Apache License 2.0 to a dual SSPL / Elastic License model. The SSPL is not an OSI-approved open-source license; the Elastic License is a source-available proprietary license. For users and vendors who depended on a genuinely open-source search engine, the ALv2 version was frozen at 7.10.2.
OpenSearch is the response: a fork of Elasticsearch 7.10.2 and Kibana 7.10.2, kept under the Apache License 2.0, created so that an open-source search engine continues to exist.
| Elasticsearch (post-Jan 2021) | OpenSearch | |
|---|---|---|
| License | SSPL / Elastic License (source-available) | Apache License 2.0 (OSI-approved) |
| Forked from | — | Elasticsearch / Kibana 7.10.2 |
| Package namespace | org.elasticsearch.* | org.opensearch.* |
| Governance | Elastic (company) | OpenSearch Software Foundation / Linux Foundation |
This is why the ALv2 commitment is non-negotiable and why the license checks below are strict: the reason the project exists is to stay Apache-2.0. A dependency or a file that compromises that is an existential problem, not a nitpick.
The SPDX Header on Every Source File
Every source file in the core repo carries an SPDX header declaring its license. This is the exact block (from the project's standard and the curriculum spec):
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
Notes that trip people up:
- The header is machine-checked. The
precommitlicense-header check fails the build (and the PR CI) if a source file is missing it or has the wrong text. - Files inherited from the Elasticsearch fork may additionally carry the original Apache-2.0 / Elastic / SSPL provenance notice plus a "Modifications Copyright OpenSearch Contributors" line — the fork preserved upstream attribution where required. New files get the clean header above.
- Comment syntax varies by language (
/* */for Java,#for shell/YAML, etc.), but the SPDX identifier and intent are constant.
Check your file before you push:
cd ~/OpenSearch
# The precommit task runs the header check among others:
./gradlew precommit
# Spot-check a header by hand:
grep -n "SPDX-License-Identifier" server/src/main/java/org/opensearch/cluster/ClusterState.java
If you add a new .java file without the header, precommit will tell you, and CI will be red
until you fix it.
Build-Time License Checks
The build enforces licensing on three fronts. Together they make it impossible to merge a licensing violation without a maintainer override.
| Check | What it verifies | Runs under |
|---|---|---|
| Source header check | Every source file has the correct SPDX/Apache-2.0 header | ./gradlew precommit |
| Dependency license check | Every third-party jar has a matching licenses/<dep>-LICENSE.txt and a licenses/<dep>.sha1 (or .jar.sha1) recording the artifact's checksum | ./gradlew precommit / dependencyLicenses |
| Allowed-licenses check | Every dependency's license is on the permitted (ALv2-compatible) list; copyleft/incompatible licenses are rejected | the dependency license tooling in precommit |
The licenses/ Directory
Each module that has third-party dependencies keeps a licenses/ directory. For every
dependency it must contain:
licenses/<dependency>-LICENSE.txt— the dependency's license text.licenses/<dependency>-NOTICE.txt— its NOTICE, where the upstream provides one.licenses/<dependency>.sha1(or<dependency>-<version>.jar.sha1) — the SHA-1 checksum pinning the exact artifact, so the jar can't be swapped without the check noticing.
cd ~/OpenSearch
# Find the license bookkeeping for a module:
find . -type d -name licenses | head
ls modules/lang-painless/licenses/ 2>/dev/null
# A mismatch between the declared sha1 and the resolved jar fails the build.
The SHA pin is the security-relevant part: it means a dependency's bytes are fixed and audited, not just its name and version.
Allowed Dependency Licenses
A dependency may be added only if its license is ALv2-compatible and permitted by the project. The general shape:
| License class | Examples | Allowed? |
|---|---|---|
| Permissive | Apache-2.0, MIT, BSD-2/3-Clause, ISC | Yes — the bread and butter |
| Weak copyleft (situational) | EPL, MPL, CDDL | Case-by-case; often restricted by how it's used/linked |
| Strong copyleft | GPL, LGPL, AGPL | No for bundled runtime dependencies — incompatible with an ALv2 distribution |
| Source-available / non-OSI | SSPL, Elastic License, "Commons Clause" | No — these are exactly what OpenSearch forked away from |
Warning: The project did not fork from Elasticsearch to permissively re-introduce SSPL-, GPL-, or Elastic-License-encumbered code through a dependency. A strong-copyleft or source-available runtime dependency is rejected on principle, not just by a linter.
The precise allowed-license set lives in the build configuration; consult it rather than guessing:
cd ~/OpenSearch
grep -rin "allowedLicenses\|licenseMapping\|SSPL\|GPL" buildSrc build-tools* 2>/dev/null | head
How Adding a Dependency Triggers License Review
Adding a new third-party library is one of the most license-sensitive things you can do in a PR. The flow:
flowchart TD
A[Add dependency to a module build.gradle] --> B[Run ./gradlew precommit]
B --> C{licenses/ entry exists?}
C -->|No| D[Build fails: missing LICENSE/NOTICE/sha1]
C -->|Yes| E{License on allowed list?}
E -->|No| F[Build fails: disallowed license]
E -->|Yes| G{sha1 matches resolved jar?}
G -->|No| H[Build fails: checksum mismatch]
G -->|Yes| I[Update NOTICE if required]
I --> J[Maintainer license review on the PR]
J --> K[Merge]
Concretely, to add a dependency you must:
- Add it to the module's
build.gradle. - Create
licenses/<dep>-LICENSE.txt,licenses/<dep>-NOTICE.txt(if upstream has one), and thelicenses/<dep>.sha1checksum file. - Ensure its license is ALv2-compatible and on the allowed list.
- Update the top-level
NOTICE.txtif the dependency's license requires attribution there. - Run
./gradlew precommituntil all license checks pass.
Then a maintainer reviews the dependency itself — is it maintained, is it the right
library, does it pull transitive dependencies with bad licenses, is it worth the supply-chain
weight? A new dependency is never just a build.gradle line; it is a license and
supply-chain decision that a maintainer signs off on. This is also why
maintainers scrutinize dependency additions more than equivalent-
sized code changes.
NOTICE Handling
The Apache License 2.0 requires that attribution notices be preserved. OpenSearch keeps a
top-level NOTICE.txt and per-module/per-dependency NOTICE files:
- When a dependency ships a
NOTICE, its content must be carried in the appropriate NOTICE file so downstream redistribution keeps the required attribution. - The fork preserved upstream Elasticsearch/Apache attribution where the original license required it.
- Dropping or mangling a required NOTICE is a license compliance defect, not a cosmetic one.
cd ~/OpenSearch
sed -n '1,20p' NOTICE.txt
What Would Block a Release on Licensing Grounds
Licensing is a hard gate at release time (it feeds the release-readiness checklist). Any of these blocks a release:
| Blocker | Why it's release-blocking |
|---|---|
| A source file missing the SPDX/Apache-2.0 header | Distribution would ship un-headered source; precommit/CI red |
| A bundled dependency with a disallowed license (GPL/AGPL/SSPL/Elastic, etc.) | Distributing it would violate the ALv2-only promise — existential |
A licenses/ entry missing or with a mismatched SHA | The exact artifact isn't pinned/audited; supply-chain integrity is unverifiable |
| A required NOTICE dropped or incomplete | ALv2 attribution non-compliance |
| A new transitive dependency that sneaks in a bad license | Same as a direct bad dependency — checked across the graph |
The throughline: OpenSearch will not ship a release it cannot defend as cleanly Apache-2.0. Every licensing check exists to make that defensible automatically, on every PR, so the release itself is never a surprise.
Prove You Understand This
- Why is OpenSearch Apache-2.0, and what specifically happened in 2021 that made the fork necessary? What version was it forked from?
- Reproduce the exact SPDX header block for a new source file, and name the check that enforces it.
- For one third-party dependency, what three kinds of files must exist under
licenses/, and what does the.sha1file protect against? - Classify these for a bundled runtime dependency: MIT, AGPL, SSPL, BSD-3-Clause, Apache-2.0. Which are rejected and why?
- Walk through everything that happens, from
build.gradleedit to merge, when you add a new dependency. - List three distinct licensing conditions that would block a release, and explain why each is non-negotiable given why the project exists.
Next: Code Style, Test Quality, and Building Trust — the everyday mechanics, including the precommit gate you just used, that earn you review bandwidth.