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
LicenseSSPL / Elastic License (source-available)Apache License 2.0 (OSI-approved)
Forked fromElasticsearch / Kibana 7.10.2
Package namespaceorg.elasticsearch.*org.opensearch.*
GovernanceElastic (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 precommit license-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.

CheckWhat it verifiesRuns under
Source header checkEvery source file has the correct SPDX/Apache-2.0 header./gradlew precommit
Dependency license checkEvery 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 checkEvery dependency's license is on the permitted (ALv2-compatible) list; copyleft/incompatible licenses are rejectedthe 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 classExamplesAllowed?
PermissiveApache-2.0, MIT, BSD-2/3-Clause, ISCYes — the bread and butter
Weak copyleft (situational)EPL, MPL, CDDLCase-by-case; often restricted by how it's used/linked
Strong copyleftGPL, LGPL, AGPLNo for bundled runtime dependencies — incompatible with an ALv2 distribution
Source-available / non-OSISSPL, 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:

  1. Add it to the module's build.gradle.
  2. Create licenses/<dep>-LICENSE.txt, licenses/<dep>-NOTICE.txt (if upstream has one), and the licenses/<dep>.sha1 checksum file.
  3. Ensure its license is ALv2-compatible and on the allowed list.
  4. Update the top-level NOTICE.txt if the dependency's license requires attribution there.
  5. Run ./gradlew precommit until 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:

BlockerWhy it's release-blocking
A source file missing the SPDX/Apache-2.0 headerDistribution 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 SHAThe exact artifact isn't pinned/audited; supply-chain integrity is unverifiable
A required NOTICE dropped or incompleteALv2 attribution non-compliance
A new transitive dependency that sneaks in a bad licenseSame 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

  1. Why is OpenSearch Apache-2.0, and what specifically happened in 2021 that made the fork necessary? What version was it forked from?
  2. Reproduce the exact SPDX header block for a new source file, and name the check that enforces it.
  3. For one third-party dependency, what three kinds of files must exist under licenses/, and what does the .sha1 file protect against?
  4. Classify these for a bundled runtime dependency: MIT, AGPL, SSPL, BSD-3-Clause, Apache-2.0. Which are rejected and why?
  5. Walk through everything that happens, from build.gradle edit to merge, when you add a new dependency.
  6. 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.