Safeguard
Supply Chain Security•

SPDX Versions Explained: 1.0 to 3.0.1 and Migrating from 2.3

SPDX went from 1.0 in 2011 to 2.3 in 2022, ISO/IEC 5962 (2.2.1) in 2021 and 3.0.1 in 2024. What each version added and how to move from 2.3.

Safeguard Security Team
39 min read

SPDX has had three generations. Versions 1.0 to 1.2 (2011 to 2013) described single packages. Versions 2.0 to 2.3 (2015 to 2022) added relationships, snippets and security fields, and 2.2.1 became ISO/IEC 5962:2021. SPDX 3.0 and 3.0.1 (2024) replaced the document model with elements, profiles and JSON-LD, so moving from 2.3 is a real conversion.

Below: every release with dates checked against the SPDX project's records, what the ISO edition covers, a field by field map from 2.3 to 3.0.1, the same SBOM in both versions (each validated), the official tools, what broke when we converted a real file, and a migration checklist.

Key takeaways

  • Three generations, two breaks. 2.0 introduced elements and relationships; 3.0 replaced the document centred model. Within a generation, releases mostly added optional fields.
  • ISO/IEC 5962:2021 is SPDX 2.2.1. Nothing from 2.3 or 3.x is in that ISO edition.
  • 3.0 dropped tag-value, YAML and spreadsheets. JSON-LD, checked against a JSON schema and SHACL shapes, is the exchange format.
  • Target 3.0.1, not 3.0. It fixed property and relationship names and Security profile cardinalities.
  • 2.3 is still maintained. A 2.3.1 maintenance release was tagged in October 2026, and most generators still emit 2.3.
  • Conversion is lossy in practice. In our test, a valid 2.3 file converted to a schema valid 3.0.1 file, but the package hash disappeared, identifiers were regenerated and a plain dependency gained a build scope.

The SPDX version timeline

Dates below come from the SPDX project's own history page on spdx.dev, the change log of the SPDX 3 model repository, and tags in the spdx-spec GitHub repository. Where those sources disagree, the table says so.

VersionDateWhat it addedSource and notes
Drafting beginsFeb 2010Work group under the Linux Foundation, first called "Package Facts"spdx.dev history
1.0Aug 2011First specification; describes a single packagespdx.dev history
1.1Aug 2012Fixed a flaw in the package verification algorithmspdx.dev history
1.2Oct 2013Better use of the SPDX License List, more project fieldsspdx.dev history
2.0May 2015SPDX elements and SPDXRef identifiers, relationships, annotations, external document references, license expression syntax, multiple packagesspdx.dev history; spec Annex I
2.1Aug 2016Snippets, external packages with filesAnalyzed false, ExternalRef on packages, guidance for SPDX identifiers in source filesspdx.dev history; GitHub release object created July 2017
2.1.1Jun 2019Specification moved from Google Docs to GitHubspdx.dev history
2.2May 2020JSON, YAML and development XML formats, SPDX Lite, SPDX File Tags, more relationship types, NONE and NOASSERTION in relationshipsspdx.dev history; GitHub release June 2020
2.2.1Prepared Aug 2020, tagged Sep 2021No technical change; reformatted for ISO submissionspec Annex I; spdx.dev history
ISO/IEC 5962:2021Aug 2021International standard based on 2.2.1spdx.dev history; spec Annex I
2.2.2Tagged Sep 2021, GitHub release Apr 2022Editorial fixes after ISO publication; JSON schema fixesspec Annex I; spdx-spec tags
2.3Aug 2022Primary package purpose, built, release and valid until dates, new hash algorithms, optional license fields, security external references, NTIA minimum element mappingspdx.dev history; tag 11 Aug 2022
3.0 release candidateMay 2023Security, Build, Dataset and AI profiles previewedspdx.dev history
3.015 Apr 2024Element model, nine profiles, JSON-LD serialisationspdx-3-model change log; spdx-spec tag
3.0.110 Dec 2024 (model), 17 Dec 2024 (spec tag)Name and cardinality fixes, Adler-32 restored, changes for the OMG and ISO submissionsspdx-3-model change log; spdx-spec release
3.1 release candidate 1Jan 2026Published for review; not a final releasespdx-spec tag and versions list
2.3.1Tagged 5 Oct 2026; change log lists 30 Oct 2026Maintenance release of 2.3: schema fixes, Annex L regulatory mapping including CISA 2026 minimum elementsspdx-spec tags and change log

Two cautions. The GitHub "release" objects for 2.0 and 2.1 were created in July 2017, long after those versions shipped, so they are not release dates. And the 2.3.1 tag and text are live while its change log names a release date a week after this post; check the spdx-spec releases page before you pin a schema to it.

SPDX 1.0, 1.1 and 1.2: one package per document

The 1.x line was built for license compliance on a single package. If you meet a 1.x document today, treat it as a license manifest for one package: it has no relationship graph, so it cannot tell you what depends on what.

SPDX 2.0: elements and relationships

SPDX 2.0 (May 2015) is the version that made SPDX a bill of materials format. According to the differences annex in the specification, it introduced the idea of an SPDX element: documents, files and packages each get an identifier beginning with SPDXRef. Any element can then have a relationship to any other element, which is how a 2.x document expresses that a package contains files, depends on another package or was generated from source.

2.0 also added the license expression syntax (the AND, OR and WITH grammar you see in fields such as licenseDeclared), the ability to reference elements in other SPDX documents, more file types and checksum algorithms, and annotations. The old Review Information section was deprecated in favour of annotations.

SPDX 2.1: snippets and external references

SPDX 2.1 added snippets, so a portion of a file can carry different licensing from the rest of it. It let a document describe external packages without listing their files, by setting filesAnalyzed to false, and it added the ExternalRef structure on packages. ExternalRef is where package URLs (purl), CPE names and later security references live in every 2.x document you see today. 2.1 also deprecated the file level "Artifact of Project" fields and added an annex on using SPDX short identifiers in source file headers, the SPDX-License-Identifier comment that many projects now carry.

SPDX 2.2, 2.2.1 and 2.2.2: new formats and the ISO edition

SPDX 2.2 (May 2020) is where JSON, YAML and a development version of XML were added as supported file formats, next to tag-value and RDF/XML. It added SPDX Lite, a lightweight subset for scenarios where a full document is not needed, and the SPDX File Tags annex. It also widened the relationship vocabulary and allowed NONE and NOASSERTION as relationship targets to say what is unknown or known to be empty.

SPDX 2.2.1 is the same content reformatted for ISO. The specification states there were no technical differences: clauses were renumbered and annexes moved from Roman numerals to letters. 2.2.2 then fixed formatting, grammar and JSON schema typos found after ISO publication, again with no new fields.

SPDX 2.3 and 2.3.1: security and interoperability

SPDX 2.3 (August 2022) is the version most tools emit today. The specification lists its additions:

  • A Primary Package Purpose field (application, library, container, operating system and others), plus Built Date, Release Date and Valid Until Date on packages.
  • New hash algorithms: SHA3-256, SHA3-384, SHA3-512, BLAKE2b-256, BLAKE2b-384, BLAKE2b-512, BLAKE3 and ADLER32.
  • Several licensing fields made optional, so producers no longer have to write NOASSERTION when they have no value.
  • Two relationship types, REQUIREMENT_DESCRIPTION_FOR and SPECIFICATION_FOR.
  • More security external reference types (advisory, fix, url and swid) and gitoid as a persistent identifier.
  • An SPDX Lite update that makes the NTIA minimum elements required, and an annex showing how those elements map to SPDX fields.

The 2.3.1 maintenance release adds no fields. Its annex says it fixes the operating system purpose enum (OPERATING-SYSTEM), states that an omitted optional field means NOASSERTION, makes the package verification code optional, requires documentNamespace in the JSON schema, adds pattern checks for identifiers, checksums and timestamps, and adds Annex L mapping the 2021 NTIA and 2026 CISA minimum elements. Expect the 2.3.1 schema to reject some files the older schema accepted.

For the regulatory side of the minimum elements, see our NTIA minimum elements field mapping.

What ISO/IEC 5962:2021 standardised, and what it did not

ISO/IEC 5962:2021 is SPDX 2.2.1. The SPDX specification describes 2.2.1 as 2.2 "reformatted for submission to ISO via the PAS process", and the project's history dates the ISO standard's availability to August 2021. The ISO catalogue page for the standard blocked our scripted fetch, so we rely on the SPDX project's own documents for its content.

What that means in practice:

  • It covers the 2.2 field set: elements, relationships, snippets, ExternalRef, license expressions, SPDX Lite and File Tags.
  • It does not include 2.3: no primary package purpose, release and build dates, SHA3 and BLAKE hashes or NTIA mapping annex.
  • It does not include 3.x. The 3.0.1 release notes say it supports the OMG and ISO submissions, but we could not confirm on a primary source that an ISO edition of SPDX 3 has been published, so do not cite one.

So when a contract says "SPDX per ISO/IEC 5962", a 2.3 document is a superset that a 2.2 aware parser will usually read, and a 3.0.1 document is a different model entirely. Our post on whether SBOM formats are official standards covers the procurement angle.

What changed in SPDX 3.0

Our earlier pieces on what changed in SPDX 3.0 and the 3.0 feature overview cover the headline features. Here we focus on the structural changes that decide how a migration goes, as written in the specification's differences annex.

SPDX now means System Package Data Exchange

Up to 2.3, SPDX stood for Software Package Data Exchange. From 3.0 the annex says the scope has widened beyond software and SPDX now means System Package Data Exchange. That is not cosmetic: the AI and Dataset profiles describe things that are not software packages at all.

Everything is an element, and elements stand alone

In 2.3, every package, file and snippet lives inside exactly one SPDX document and inherits its namespace. In 3.0 the annex states the principle directly: every element can be expressed and referenced independently of any other element, including the document. A package can stand alone or sit inside an SpdxDocument, Sbom or Bundle, with any namespace. Consequences:

  • Relationships and annotations are elements, with their own from and to, so a third party can publish a relationship or annotation about a package it did not create.
  • Identifiers are full IRIs. They look like URLs, but the getting started guide is clear they are identifiers, not fetchable locations.
  • External document references split in two: NamespaceMap for short prefixes, and ExternalMap for elements defined elsewhere with how to verify and locate them.

Profiles

SPDX 3.0.1's conformance clause defines nine profiles, which are compliance points: Core, Software, Security, Licensing, Dataset, AI, Build, Lite and Extension. Core is mandatory and all others are optional. Licensing comes in two forms, SimpleLicensing (a license expression as a string) and ExpandedLicensing (license expressions as structured objects). A document declares which profiles it conforms to in profileConformance on the SpdxDocument.

A tool can claim conformance to the Software profile without supporting Security or AI. That matters when you buy or build tooling: "supports SPDX 3" says nothing until you know which profiles. Our post on SPDX 3 profiles beyond software goes through the Build, Security, Licensing, Lite and Extension profiles field by field.

JSON-LD serialisation

SPDX 3 data is RDF underneath. The 3.0.1 serialisation clause says any RDF serialisation can carry SPDX data, and then defines the JSON-LD form that tools actually exchange: every document references the global context at https://spdx.org/rdf/3.0.1/spdx-context.jsonld, and spdxId and type are aliases for the JSON-LD @id and @type keywords. A document conforms when it passes two checks: structural validation against the SPDX JSON schema and semantic validation against the SPDX OWL ontology, which carries SHACL shapes.

The getting started annex adds a useful point for developers: SPDX JSON-LD is designed to be a strict subset of JSON-LD, so you can read it with a plain JSON parser if all you need is the data, or with a full JSON-LD processor if you want linked data.

The clause also defines a canonical serialisation: JSON with no line breaks, no whitespace outside strings and object keys sorted by name. It exists so that the same content always produces the same bytes, which matters when you hash or sign a document.

What 3.0.1 fixed

The spdx-3-model change log, dated 10 December 2024, lists the 3.0.1 changes. The ones that break naive tooling written against 3.0:

Change in 3.0.1Before (3.0)After (3.0.1)
Core property nameimportsimport
Build property nameparametersparameter
Relationship type nameshasInputs, hasOutputshasInput, hasOutput
Relationship type spellinghasPrerequsitehasPrerequisite
Licensing relationship names in profile conformance textconcludedLicense, declaredLicensehasConcludedLicense, hasDeclaredLicense
Software contentTypeSoftware profile had its own contentTyperemoved; File uses the Core contentType
VEX affected assessmentactionStatement 0..1, actionStatementTime 0..*actionStatement 1..1, actionStatementTime 0..1
Hash algorithmsno Adler-32adler32 restored from 2.3
IndividualsnoneSpdxOrganization individual and IndividualElement class added

The spec repository's release notes add the Package URL specification and license matching guidance as annexes; our post on the 3.0.1 patch release covers the submission story.

The practical rule: emit 3.0.1. The context and schema URLs carry the version, and in our test the 3.0.1 JSON schema rejected a CreationInfo whose specVersion was "3.0". The annex also requires a full semantic version with a patch number.

Serialisations across versions

The file formats post compares formats for 2.x in depth. This table is the version view.

Serialisation1.x to 2.12.2 to 2.3.13.0 and 3.0.1
Tag-value (.spdx)YesYesNot supported
RDF/XMLYesYesAllowed as an RDF serialisation by clause 6; the differences annex lists RDF/XML as not supported. Use JSON-LD for exchange
Spreadsheet (XLS, XLSX)Supported by SPDX toolsSupported by SPDX toolsNot supported
JSONNoYes, with a JSON schemaReplaced by JSON-LD
YAMLNoYesNot supported
XML (non RDF)NoDevelopment version from 2.2Not supported
JSON-LDNoNoYes; JSON schema plus OWL and SHACL
Turtle, N-TriplesNoTurtle output via SPDX toolsAllowed as RDF serialisations
Canonical JSONNoNoDefined for deterministic bytes

The 3.0.1 documents disagree slightly here. Clause 6 allows any RDF serialisation, naming RDF/XML among them, while the informative differences annex lists RDF/XML as not supported. Our reading: only JSON-LD has the schema, examples and tool support, so exchange 3.x in JSON-LD. The 3.1 release candidate calls its JSON form "SPDX 3 JSON", a strict subset of JSON-LD.

Field by field: SPDX 2.3 to 3.0.1

These tables come from the "Differences between V3.0 and V2.3" section of the specification's annex, which is maintained in the spdx/using repository and linked from the 3.0.1 specification. JSON-LD names are shown as they appear in serialised documents: properties from the Core profile have no prefix, and properties from other profiles carry a prefix such as software_ or simplelicensing_.

Document and creation information

SPDX 2.3SPDX 3.0.1Notes
spdxVersion "SPDX-2.3"CreationInfo specVersion "3.0.1"Must be full semantic version
SPDXID SPDXRef-DOCUMENTSpdxDocument with an IRI spdxIdDocument is now an element
documentNamespaceNamespace part of each spdxId; NamespaceMap for prefixesElements may use different namespaces
creationInfo.creators "Person: ..."createdBy pointing to a Person or Organization elementcreatedBy is required
creationInfo.creators "Tool: ..."createdUsing pointing to a Tool elementIf only a tool is known, the annex says to also create a SoftwareAgent for createdBy
creationInfo.createdCreationInfo createdShared CreationInfo objects are referenced by every element
dataLicense CC0-1.0dataLicense on the documentStill CC0-1.0
documentDescribes, DESCRIBESrootElement, plus a describes relationship if wantedSbom and SpdxDocument both have rootElement
externalDocumentRefsimport (ExternalMap) and namespaceMapDocument checksum becomes verifiedUsing
licenseListVersion "3.20"Semantic version, for example "3.20.0"Add a patch number

Package

SPDX 2.3SPDX 3.0.1Notes
namenameInherited from Element
versionInfosoftware_packageVersionRenamed for consistency with tag-value
suppliersuppliedBy (an Agent element)Extra suppliers via availableFrom relationships
originatororiginatedBy (Agent elements)
downloadLocationsoftware_downloadLocation
homepagesoftware_homePage
primaryPackagePurposesoftware_primaryPurpose, plus software_additionalPurposeNow on files and snippets too
releaseDate, builtDate, validUntilDatereleaseTime, builtTime, validUntilTime (Core, no prefix)Names reflect time granularity. The annex text says buildTime, but the 3.0.1 JSON schema uses builtTime
checksumsverifiedUsing with Hash objects; the algorithm uses HashAlgorithm values such as sha256"Hash" replaces "checksum"
packageFileName with packageChecksumA File element linked by hasDistributionArtifact
externalRefs purlsoftware_packageUrl, or an ExternalIdentifierSingle purl without comment goes to packageUrl
externalRefs cpe22Type, cpe23Type, swidexternalIdentifierIdentifiers separated from references
externalRefs gitoid, swhsoftware_contentIdentifier
externalRefs advisory, fix, other security typesexternalRef (ExternalRef kept)The url type becomes securityOther
filesAnalyzedRemovedThe annex notes there is no longer a way to say file checksums are required
licenseInfoFromFilesRemovedSuggested as an annotation on the package
copyrightTextsoftware_copyrightText

File and Snippet

SPDX 2.3SPDX 3.0.1Notes
fileNamename
fileTypes enumcontentType (IANA media type) plus software_primaryPurposeSOURCE becomes purpose source; TEXT becomes text/plain
fileContributorsoriginatedBy (Person elements)
checksumsverifiedUsing
Snippet ranges with StartEndPointersoftware_byteRange and software_lineRange as PositiveIntegerRangeByte range now optional
snippetFromFileA contains relationship from the File to the Snippet

Relationships

SPDX 2.3SPDX 3.0.1Swap from and to?
CONTAINScontainsNo
CONTAINED_BYcontainsYes
DEPENDS_ONdependsOn, optionally with a lifecycle scopeNo
DEPENDENCY_OFdependsOnYes
BUILD_DEPENDENCY_OFdependsOn with scope buildYes
DEV_DEPENDENCY_OFdependsOn with scope developmentYes
TEST_DEPENDENCY_OFdependsOn with scope testYes
RUNTIME_DEPENDENCY_OFdependsOn with scope runtimeYes
GENERATED_FROMgeneratesYes
DESCRIBED_BYdescribesYes
PATCH_FOR, PATCH_APPLIEDpatchedByYes
STATIC_LINKhasStaticLinkNo
DYNAMIC_LINKhasDynamicLinkYes
Target NONEcompleteness complete
Target NOASSERTIONcompleteness noAssertion

The swap column is where hand written converters go wrong. 3.0 relationships have a single direction, so every "_OF" type in 2.3 flips. A tool that copies spdxElementId to from without checking the table will invert half your dependency graph.

Licensing

SPDX 2.3SPDX 3.0.1Notes
licenseConcludedhasConcludedLicense relationship to a license elementRelationship names corrected in 3.0.1
licenseDeclaredhasDeclaredLicense relationship
License expression stringsimplelicensing_LicenseExpression, or ExpandedLicensing objectsTwo profiles carry the same information
hasExtractedLicensingInfos (LicenseRef)CustomLicense
LicenseExceptionListedLicenseException (additionId, additionText, additionName)
WITH operatorWithAdditionOperator (subjectLicense, subjectAddition)Covers custom additions too
licenseId, licenseName, licenseCommentspdxId, name, commentLicenses are elements now
LicenseInfoInFile on PackageRemovedAnnotation suggested

If your team relies on the difference between declared and concluded licenses, our declared versus concluded license guide explains why it matters. In 3.0.1 that difference survives, but it moves from two fields to two relationship types.

The same SBOM in SPDX 2.3 and SPDX 3.0.1

Here is a deliberately small SBOM: an application, example-app 1.4.0, that depends on lodash 4.17.21. Both files below are real, minimal and valid.

SPDX 2.3 JSON

This document passed the SPDX 2.3 JSON schema from the spdx-spec repository's support/2.3 branch (titled SPDX 2.3.1), the SPDX Java tools' Verify command (tools-java 2.0.7) and the SPDX Python tools (spdx-tools 0.8.3). The lodash SHA-1 value is the one the npm registry publishes for that tarball.

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "example-app-1.4.0",
  "documentNamespace": "https://example.com/spdxdocs/example-app-1.4.0-6f1c2b0e-3a51-4c6e-9d0a-2b7e1f4c8a90",
  "creationInfo": {
    "created": "2026-10-20T09:00:00Z",
    "creators": ["Organization: Example Corp", "Tool: example-sbom-builder-2.1.0"]
  },
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-example-app",
      "name": "example-app",
      "versionInfo": "1.4.0",
      "supplier": "Organization: Example Corp",
      "downloadLocation": "NOASSERTION",
      "filesAnalyzed": false,
      "primaryPackagePurpose": "APPLICATION",
      "licenseDeclared": "Apache-2.0"
    },
    {
      "SPDXID": "SPDXRef-Package-lodash",
      "name": "lodash",
      "versionInfo": "4.17.21",
      "supplier": "NOASSERTION",
      "downloadLocation": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "filesAnalyzed": false,
      "checksums": [
        { "algorithm": "SHA1", "checksumValue": "679591c564c3bffaae8454cf0b3df370c3d6911c" }
      ],
      "licenseConcluded": "MIT",
      "licenseDeclared": "MIT",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:npm/lodash@4.17.21"
        }
      ]
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-DOCUMENT",
      "relationshipType": "DESCRIBES",
      "relatedSpdxElement": "SPDXRef-Package-example-app"
    },
    {
      "spdxElementId": "SPDXRef-Package-example-app",
      "relationshipType": "DEPENDS_ON",
      "relatedSpdxElement": "SPDXRef-Package-lodash"
    }
  ]
}

SPDX 3.0.1 JSON-LD

The same content written by hand for 3.0.1. It passed structural validation against https://spdx.org/schema/3.0.1/spdx-json-schema.json, semantic validation against the SHACL shapes in https://spdx.org/rdf/3.0.1/spdx-model.ttl, and the SPDX Java tools' Verify command.

{
  "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
  "@graph": [
    {
      "type": "CreationInfo",
      "@id": "_:creationinfo",
      "specVersion": "3.0.1",
      "created": "2026-10-20T09:00:00Z",
      "createdBy": ["https://example.com/spdx/agent/example-corp"]
    },
    {
      "type": "Organization",
      "spdxId": "https://example.com/spdx/agent/example-corp",
      "creationInfo": "_:creationinfo",
      "name": "Example Corp"
    },
    {
      "type": "SpdxDocument",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/document",
      "creationInfo": "_:creationinfo",
      "name": "example-app-1.4.0",
      "profileConformance": ["core", "software", "simpleLicensing"],
      "rootElement": ["https://example.com/spdx/example-app-1.4.0/sbom"],
      "element": [
        "https://example.com/spdx/example-app-1.4.0/sbom",
        "https://example.com/spdx/example-app-1.4.0/pkg-app",
        "https://example.com/spdx/example-app-1.4.0/pkg-lodash",
        "https://example.com/spdx/example-app-1.4.0/rel-depends",
        "https://example.com/spdx/example-app-1.4.0/lic-mit",
        "https://example.com/spdx/example-app-1.4.0/rel-lic"
      ]
    },
    {
      "type": "software_Sbom",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/sbom",
      "creationInfo": "_:creationinfo",
      "software_sbomType": ["build"],
      "rootElement": ["https://example.com/spdx/example-app-1.4.0/pkg-app"],
      "element": [
        "https://example.com/spdx/example-app-1.4.0/pkg-app",
        "https://example.com/spdx/example-app-1.4.0/pkg-lodash",
        "https://example.com/spdx/example-app-1.4.0/rel-depends",
        "https://example.com/spdx/example-app-1.4.0/lic-mit",
        "https://example.com/spdx/example-app-1.4.0/rel-lic"
      ]
    },
    {
      "type": "software_Package",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/pkg-app",
      "creationInfo": "_:creationinfo",
      "name": "example-app",
      "software_packageVersion": "1.4.0",
      "suppliedBy": "https://example.com/spdx/agent/example-corp",
      "software_primaryPurpose": "application"
    },
    {
      "type": "software_Package",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/pkg-lodash",
      "creationInfo": "_:creationinfo",
      "name": "lodash",
      "software_packageVersion": "4.17.21",
      "software_packageUrl": "pkg:npm/lodash@4.17.21",
      "software_downloadLocation": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "verifiedUsing": [
        {
          "type": "Hash",
          "algorithm": "sha1",
          "hashValue": "679591c564c3bffaae8454cf0b3df370c3d6911c"
        }
      ]
    },
    {
      "type": "LifecycleScopedRelationship",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/rel-depends",
      "creationInfo": "_:creationinfo",
      "from": "https://example.com/spdx/example-app-1.4.0/pkg-app",
      "relationshipType": "dependsOn",
      "to": ["https://example.com/spdx/example-app-1.4.0/pkg-lodash"],
      "scope": "runtime"
    },
    {
      "type": "simplelicensing_LicenseExpression",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/lic-mit",
      "creationInfo": "_:creationinfo",
      "simplelicensing_licenseExpression": "MIT"
    },
    {
      "type": "Relationship",
      "spdxId": "https://example.com/spdx/example-app-1.4.0/rel-lic",
      "creationInfo": "_:creationinfo",
      "from": "https://example.com/spdx/example-app-1.4.0/pkg-lodash",
      "relationshipType": "hasDeclaredLicense",
      "to": ["https://example.com/spdx/example-app-1.4.0/lic-mit"]
    }
  ]
}

Read the two side by side and the model change is obvious. The 2.3 file nests packages and relationships inside the document. The 3.0.1 file is a flat graph: a shared CreationInfo, an Organization that both created the document and supplied the app, a document whose root is an Sbom, two packages, and three relationship and license elements that point at the packages by IRI. The 3.0.1 file is longer, and that is normal. In exchange, any element in it can be referenced from another document without copying it.

Step by step: validate and convert with the official tools

The walkthrough below uses only tools published by the SPDX project, plus general purpose JSON schema and SHACL libraries for the structural and semantic checks that the 3.0.1 specification defines. We ran every command shown here on 23 October 2026.

Step 1: find out what you have

# 2.x JSON documents carry spdxVersion at the top level
jq -r '.spdxVersion' app.spdx.json          # SPDX-2.3

# 3.x JSON-LD documents carry specVersion inside CreationInfo
jq -r '."@graph"[] | select(.type=="CreationInfo") | .specVersion' app.spdx3.json   # 3.0.1

# Tag-value files start with a version line
head -1 app.spdx                             # SPDXVersion: SPDX-2.3

If a file has spdxVersion SPDX-2.2 or earlier, the 2.3 tools read it. If it has an @context ending in /3.0/ rather than /3.0.1/, it was produced against 3.0 and needs the 3.0.1 name fixes before it will validate.

Step 2: validate 2.3 with the SPDX Python tools

The tools-python README says version 0.8 fully validates SPDX 2.2 and 2.3 documents in tag-value, RDF, JSON, YAML and XML, and converts between those formats.

python3 -m venv .venv && . .venv/bin/activate
python3 -m pip install spdx-tools

# Parse and validate; prints validation messages if the document is invalid
pyspdxtools -i app.spdx.json

# Convert 2.3 JSON to tag-value (the output format follows the file extension)
pyspdxtools -i app.spdx.json -o app.spdx

Two limits from the same README: its SPDX 3 support is experimental, "neither complete nor stable", not recommended for production, and it can only write SPDX 3, not read it. There is a bump_from_spdx2 module that converts a 2.x document into SPDX 3 elements in Python. The README describes it in terms of SPDX 3.0, so check its output against the 3.0.1 schema before trusting it.

Step 3: verify and convert with the SPDX Java tools

The tools-java README states that the tool supports SPDX 2.0, 2.1, 2.2, 2.3 and 3.0.1, converts between tag-value, RDF/XML, XLSX, XLS, JSON, XML, YAML and JSON-LD (for 3.0.1), and can verify, compare and pretty print documents. Version 2.0.7 is the current release on Maven Central.

curl -LO https://repo1.maven.org/maven2/org/spdx/tools-java/2.0.7/tools-java-2.0.7-jar-with-dependencies.jar
TOOLS="java -jar tools-java-2.0.7-jar-with-dependencies.jar"

# Verify a 2.3 document
$TOOLS Verify app.spdx.json
# This SPDX Document is valid.

# Convert 2.3 JSON to 3.0.1 JSON-LD (input and output formats given explicitly)
$TOOLS Convert app.spdx.json app.spdx3.jsonld JSON JSONLD

# Verify the 3.0.1 result
$TOOLS Verify app.spdx3.jsonld JSONLD

The README also says you can trigger the 2 to 3 conversion by naming the output with a .spdx3.json, .jsonld.json or .jsonld extension, and it points to the SPDX Online Tools at tools.spdx.org for browser based validation, comparison and conversion.

Step 4: run the two checks the 3.0.1 specification defines

Clause 6 of the 3.0.1 specification says a JSON-LD document conforms when it passes the JSON schema (structure) and the OWL ontology with SHACL shapes (semantics). This is how we checked the hand written example above:

curl -LO https://spdx.org/schema/3.0.1/spdx-json-schema.json
curl -LO https://spdx.org/rdf/3.0.1/spdx-model.ttl
curl -LO https://spdx.org/rdf/3.0.1/spdx-context.jsonld
python3 -m pip install jsonschema pyshacl
import json, jsonschema
from rdflib import Graph
from pyshacl import validate

doc = json.load(open("app.spdx3.json"))

# 1. Structural validation against the SPDX 3.0.1 JSON schema
schema = json.load(open("spdx-json-schema.json"))
errors = list(jsonschema.Draft202012Validator(schema).iter_errors(doc))
print("schema errors:", len(errors))

# 2. Semantic validation against the SHACL shapes in the SPDX model
doc["@context"] = json.load(open("spdx-context.jsonld"))["@context"]  # use the local copy
data = Graph().parse(data=json.dumps(doc), format="json-ld")
shapes = Graph().parse("spdx-model.ttl", format="turtle")
conforms, _, report = validate(data, shacl_graph=shapes, ont_graph=shapes)
print("SHACL conforms:", conforms)

For our hand written 3.0.1 file, both checks passed. One caution for the SHACL step: when we ran it on the tools-java output, the shapes flagged relationships whose to field pointed at listed license IRIs such as http://spdx.org/licenses/MIT, because those license elements are not defined inside the document. The JSON schema and the SPDX Java tools' own Verify accepted the same file. Decide up front how your semantic validator should treat references to the SPDX License List, or you will chase false failures.

Step 5: put it in CI

A minimal job that fails the build when a generated SPDX file does not validate. This is a sketch for GitHub Actions; replace the generation step with whatever produces your SBOM.

name: spdx-validate
on: [push, pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
      - name: Fetch SPDX tools
        run: curl -sSLO https://repo1.maven.org/maven2/org/spdx/tools-java/2.0.7/tools-java-2.0.7-jar-with-dependencies.jar
      - name: Verify SPDX 2.3 SBOM
        run: java -jar tools-java-2.0.7-jar-with-dependencies.jar Verify sbom/app.spdx.json
      - name: Convert to 3.0.1 and verify
        run: |
          java -jar tools-java-2.0.7-jar-with-dependencies.jar Convert sbom/app.spdx.json sbom/app.spdx3.jsonld JSON JSONLD
          java -jar tools-java-2.0.7-jar-with-dependencies.jar Verify sbom/app.spdx3.jsonld JSONLD

Pin the tools version, as above, so a new release cannot change your output silently.

What the conversion actually did to our file

We converted the 2.3 example above with tools-java 2.0.7 and compared the result with the hand written 3.0.1 version. The output was valid against the 3.0.1 JSON schema. It was not the same SBOM.

What we checked2.3 inputtools-java 3.0.1 output
lodash SHA-1 hashPresent in checksumsNot present anywhere in the output
Element identifiersSPDXRef-Package-lodash, SPDXRef-Package-example-appGenerated IRIs ending SPDXRef-gnrtd3, SPDXRef-gnrtd5 under the namespace plus a -specv3 suffix
DEPENDS_ONNo lifecycle scopeLifecycleScopedRelationship dependsOn with scope build and completeness noAssertion
purlExternalRef of type purlsoftware_packageUrl, as the annex recommends
licenseConcluded on the appOmittedhasConcludedLicense pointing to NoAssertionLicense, consistent with the 2.3.1 rule that omission means NOASSERTION
Supplier "Organization: Example Corp"Two creator and supplier stringsThree separate Organization elements all named Example Corp
copyrightTextOmittedsoftware_copyrightText set to an empty string
Tool creator"Tool: example-sbom-builder-2.1.0"A Tool element named example-sbom-builder-2.1.0 under createdUsing

The annex leaves room for interpretation, and the scope is a tool choice, so none of this breaks the spec. It does change meaning. The lost hash weakens integrity checks, a build scope on a runtime dependency misleads anyone filtering out build only components, and regenerated identifiers break any VEX statement or ticket that referenced the old SPDXRef names. Convert, then diff the facts you care about (names, versions, purls, hashes, licenses and dependency edges), not the bytes.

Which official tools convert or validate 2.3 and 3.0.1

Tool (SPDX project)ReadsWritesValidatesWhat it does not handle, per its README
tools-java 2.0.72.0 to 2.3 in tag-value, RDF/XML, JSON, XML, YAML, XLS, XLSX; 3.0.1 JSON-LDSame formats; 2.x to 3.0.1 conversionVerify command for 2.x and 3.0.1The README documents the 2 to 3 direction; it does not describe converting 3.0.1 back to 2.3
tools-python (spdx-tools 0.8)2.2 and 2.3 in tag-value, RDF, JSON, YAML, XMLSame formats; experimental SPDX 3 writerFull 2.2 and 2.3 validationCannot read SPDX 3; SPDX 3 support not production ready
spdx-python-modelSPDX 3SPDX 3Generated from the RDF and SHACL definitionsLow level bindings without higher level helpers
SPDX Online Tools (tools.spdx.org)Uploaded documentsConverted documentsBrowser based validationNot suited to private documents you cannot upload
SPDX 3.0.1 JSON schema and OWL with SHACLAny JSON-LD documentn/aStructural and semantic conformance per clause 6Needs a JSON schema validator and a SHACL engine of your choice

For conversion between SPDX and CycloneDX rather than between SPDX versions, see what gets lost converting between SPDX and CycloneDX.

Why most tools still emit 2.3, and when to switch

Our post on why your SBOM tool outputs SPDX 2.3 when 3.0 is current covers the ecosystem reasons. The short version, from what the primary sources show:

  • Readers lag writers. The SPDX project's own Python library cannot read SPDX 3 yet, so consumers built on it cannot ingest 3.0.1.
  • 2.3 maps to the regulations people cite. Its annexes map the NTIA minimum elements, and 2.3.1 adds the 2026 CISA minimum elements.
  • 3.x is still moving. A 3.1 release candidate was published in January 2026.

That does not make 3.0.1 premature. Use this table to decide.

Your situationEmitWhy
Customers and regulators ask for "an SPDX SBOM" with minimum elements2.3 JSON (validate against the 2.3.1 schema)Widest reader support; documented minimum element mapping
You need AI models, datasets or build provenance in the same graph3.0.1 JSON-LD with the AI, Dataset or Build profiles2.3 has no equivalent fields
You exchange VEX style assessments inside SPDX3.0.1 with the Security profile2.3 only links to external advisories
You publish elements that others reference across documents3.0.1Independent elements and ExternalMap
Your consumers run tools that only read 2.x2.3, and 3.0.1 alongside if you canDo not force a format a reader cannot parse
A contract cites ISO/IEC 5962:20212.3 or 2.2.xThe ISO edition is 2.2.1; 2.3 is a compatible superset for most readers
You currently emit 3.0 (not 3.0.1)3.0.1Fix the renamed properties and the context URL

Migration checklist

For producers

  1. Inventory what you emit today: every pipeline, version and serialisation. Tag-value and YAML have no 3.x equivalent.
  2. Validate 2.3 output against the 2.3.1 schema first and fix namespace, identifier and timestamp errors before converting.
  3. Pick the profiles you will declare. Core and Software at minimum; others only if you populate them.
  4. Design stable IRIs in a namespace you control, so VEX and annotations can point at them across rebuilds.
  5. Map identifiers deliberately: purl to software_packageUrl, CPE and SWID to externalIdentifier, gitoid to contentIdentifier.
  6. Flip relationship directions using the annex table, and add lifecycle scopes only where you know them.
  7. Keep hashes in verifiedUsing, and check the output contains them.
  8. Run both checks (JSON schema and SHACL) plus the SPDX Java tools' Verify.
  9. Publish 2.3 and 3.0.1 side by side during the transition, and diff the facts in CI.
  10. Pin converter and schema versions, and record them in createdUsing.

For consumers

  1. Detect the version before parsing. spdxVersion means 2.x; an @context with /rdf/3.0.1/ means 3.0.1.
  2. Route 3.0.1 to a 3.x capable reader instead of rejecting it when a 2.x parser fails.
  3. Normalise both into one internal model keyed on purl, name, version and hash, not SPDX identifiers.
  4. Resolve relationship direction from the type, not the field position.
  5. Treat missing as unknown. Since 2.3.1, an omitted optional field means NOASSERTION; in 3.x, completeness says whether a relationship list is complete.
  6. Check profileConformance before expecting Security, Build or AI data.
  7. Keep the original file next to anything you converted it into.

Common mistakes

  • Writing specVersion "3.0" or "3.0.0" with 3.0.1 names. The context URL, schema and specVersion must all say 3.0.1.
  • Copying spdxElementId into from for every relationship type. All the "_OF" relationships flip direction in 3.0.
  • Assuming a converter keeps hashes, identifiers and scopes. In our test it kept none of the three as written.
  • Calling 2.3 "the ISO standard". ISO/IEC 5962:2021 is 2.2.1.
  • Declaring profiles you do not populate. profileConformance is a promise about content.
  • Pinning to a release candidate. 3.1-RC1 is for review; declare 3.0.1 until 3.1 is final.
  • Validating only the JSON schema. The spec requires the SHACL semantic check too.
  • Dropping licenses in a security migration. licenseConcluded and licenseDeclared become relationships; lose them and license compliance breaks downstream.

Where Safeguard fits

Safeguard's SBOM generation and analysis is generally available. It produces SPDX in JSON, XML and RDF and CycloneDX in JSON and XML, with more than 30 export formats including CSV, Excel and PDF, and it can run on every push, on a schedule or on release. Its license compliance, also generally available, parses SPDX license expressions, applies license policy and generates NOTICE and attribution files, which is the part of an SPDX document that most migrations damage. Through generally available third party risk management you can ask vendors for their SBOMs and track supplier risk, and generally available CSAF and VEX upload lets you bring vulnerability status documents in next to them.

The honest limit: Safeguard is not an SPDX version converter. It does not translate 2.3 documents into 3.0.1 beyond its listed export formats, and this post does not claim which SPDX version each export targets; check the product documentation for that before you rely on it. For the conversion step itself, use the SPDX project's tools described above and validate their output.

FAQ

What is the latest version of SPDX?

The latest final release of SPDX 3 is 3.0.1, published in December 2024. The 2.x line also has a 2.3.1 maintenance release tagged in October 2026. A 3.1 release candidate was published in January 2026 for review and is not a final release.

Is SPDX 3.0 backward compatible with SPDX 2.3?

No. SPDX 3.0 replaced the document centred model with independent elements, turned relationships and annotations into elements, renamed many properties and dropped tag-value, YAML and spreadsheet serialisations. A 2.3 parser cannot read a 3.0.1 file, so documents must be converted.

Which SPDX version is ISO/IEC 5962:2021?

ISO/IEC 5962:2021 is SPDX 2.2.1. The SPDX specification describes 2.2.1 as 2.2 reformatted for the ISO submission with no technical changes. Fields added in 2.3 and anything from 3.x are not part of that ISO edition.

What is the difference between SPDX 3.0 and 3.0.1?

3.0.1 fixes property and relationship names that were misspelled or pluralised in 3.0 (import, parameter, hasInput, hasOutput, hasPrerequisite), corrects cardinalities in the Security profile's VEX affected assessment, removes a duplicate contentType, restores the Adler-32 hash and adds changes for the OMG and ISO submissions. Tools should emit 3.0.1.

Can SPDX 3.0 use tag-value format?

No. The specification's differences annex says tag-value, YAML, RDF/XML and spreadsheet formats are not supported in 3.0. Use the JSON-LD serialisation with the 3.0.1 context. Clause 6 allows other RDF serialisations, but JSON-LD is the one with a schema and tool support.

How do I convert SPDX 2.3 to SPDX 3.0.1?

The SPDX Java tools convert 2.x documents to 3.0.1 JSON-LD with the Convert command and the JSON and JSONLD format arguments. Validate the result against the 3.0.1 JSON schema and SHACL shapes, then compare names, versions, purls, hashes, licenses and dependency edges with the original, because conversions can change them.

Why does my SBOM tool still output SPDX 2.3?

Because most consumers still read 2.3, the SPDX project's Python library cannot read SPDX 3 yet, and 2.3 has documented mappings to the NTIA and CISA minimum elements. 2.3 is still maintained. Emit 3.0.1 when you need its profiles or independent elements, ideally alongside 2.3.

Does SPDX 3 still have license concluded and license declared?

Yes, as relationships. licenseConcluded becomes a hasConcludedLicense relationship and licenseDeclared becomes hasDeclaredLicense, each pointing to a license element. License expressions can be carried as simple strings (SimpleLicensing) or structured objects (ExpandedLicensing).

Fix it without waiting for upstream

An SPDX document, in any version, only tells you which component and version you ship; it does not ship the fixed build. Migrating formats changes how that list is written, so the remediation work that follows a finding still has to happen in your dependencies and images, and that is where the tools below apply.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show Safeguard workflows that sit around SBOM work rather than SPDX conversion itself. To try them on your own machine, download Safeguard and read the CLI documentation.

The first demo is a real CLI session. After install, safeguard opens the interactive terminal, you ask it to fix the lodash CVE in a repository, and it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, and pushes nothing. The same lodash entry appears in both SBOM examples in this post; an SBOM in any version tells you where it is, and this is the step that changes it.

Click through it at your own pace, or open it full screen.

The second demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. If your SBOMs and the code they describe cannot leave your network, this is how scanning runs locally instead.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and shows how you choose which tools Claude can use. Teams that already ask an assistant questions about their projects can use the same connection to query the inventory behind their SBOMs.

Click through it at your own pace, or open it full screen.

Quick check

An SPDX 2.3 relationship says lodash DEPENDENCY_OF example-app. Which way does the 3.0.1 relationship point?

From example-app to lodash, as dependsOn. Every relationship type ending in _OF swaps direction in 3.0, according to the specification's translation table.

A contract requires SPDX "as standardised in ISO/IEC 5962:2021". Which SPDX version is that, and is a 3.0.1 document a match?

ISO/IEC 5962:2021 is SPDX 2.2.1. A 2.3 document is a near superset that most 2.2 readers accept; a 3.0.1 document uses a different model and would need agreement from the buyer.

Sources

Last reviewed: 23 October 2026. SPDX 2.3.1 and 3.1 are in motion; check the spdx-spec releases page for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.