<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>CycloneDX on Sven Ruppert</title><link>https://svenruppert.com/tags/cyclonedx/</link><description>Sven Ruppert — Java Veteran, Speaker, Trainer &amp; Bushcrafter. Articles, talks, workshops and videos on Core Java, Cybersecurity, Vaadin and Developer Relations.</description><generator>Hugo</generator><language>en</language><managingEditor>sven.ruppert@gmail.com (Sven Ruppert)</managingEditor><webMaster>sven.ruppert@gmail.com (Sven Ruppert)</webMaster><copyright>© 2026 Sven Ruppert</copyright><atom:link href="https://svenruppert.com/tags/cyclonedx/index.xml" rel="self" type="application/rss+xml"/><image><url>https://svenruppert.com/img/sven-ruppert.jpg</url><title>Sven Ruppert</title><link>https://svenruppert.com/tags/cyclonedx/</link></image><lastBuildDate>Thu, 24 Sep 2026 10:00:00 +0200</lastBuildDate><item><title>Four Delivery Formats, One SBOM</title><link>https://svenruppert.com/posts/four-delivery-formats-one-sbom/</link><pubDate>Thu, 24 Sep 2026 10:00:00 +0200</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/four-delivery-formats-one-sbom/</guid><description>One Vaadin application, four delivery formats, one SBOM: merging the Maven and npm sides into 218 components with CycloneDX — and shipping the document with the app.</description><content:encoded>&lt;![CDATA[<p>First of two articles on software bills of materials. One application, four delivery formats, one server — and the question of what the packaging reveals about the supply chain. All numbers come from an actual run; wherever an expectation was not confirmed, the text says so.</p><h2 id="the-question-every-incident-starts-with">The Question Every Incident Starts With</h2><p>A vulnerability is published. Within a few hours, the same question comes up in
every company that operates software:<strong>Are we affected?</strong></p><p>The question sounds simple. It is not — and the reason rarely lies in the
vulnerability. It lies in your own delivery. Anyone who wants to answer the
question has to know which parts are inside the artifact running on the server.
Not which dependencies are listed in the<code>pom.xml</code>. Not which ones the
development environment resolved. But which ones were shipped.</p><p>Between these three sets lie differences, and this article is about them.</p><h3 id="what-an-sbom-is">What an SBOM Is</h3><p>An SBOM — Software Bill of Materials — is a parts list. It names the components
of a piece of software with name, version, and, where known, origin and
license. The widespread format is<a href="https://8g8.eu/7faagi">CycloneDX</a>; it is generated by tools that
accompany the build process.</p><p>That is all it is, and that is precisely where its value lies: a bill of
materials is a sober document. It claims nothing about quality, security, or
suitability. It says what is included.</p><h3 id="what-an-sbom-is-not">What an SBOM Is Not</h3><p>It is<strong>not a statement about being affected</strong>. If<code>jsoup 1.22.2</code> appears in the
list and an advisory is published for that version, then the component is
included — whether the vulnerable code path is ever reached in your own
operation is not stated there. That assessment is a separate process with a
format of its own, and it presupposes the bill of materials instead of
replacing it.</p><p>Nor is it<strong>a description of the artifact</strong>. That is the more uncomfortable
point, and it is the core of this article: an SBOM describes what the build
process resolved. What part of that ends up in the shipped file is a different
question — one the document neither asks nor answers.</p><h3 id="the-structure-of-this-article">The Structure of This Article</h3><p>The subject is<strong>one</strong> application, delivered in<strong>four</strong> formats, on<strong>one</strong>
server. This setup is the real payoff: because nothing changes except the
packaging, every difference in the bill of materials can be attributed to the
packaging and to nothing else.</p><p>The four formats are covered in chapter 2. Chapter 3 pits them against each
other and delivers the finding the rest of the article works its way along.
Chapter 4 names three gaps, chapter 5 closes the largest of them, chapter 6
checks the result against the artifact, and chapter 7 brings the document to
where the question is asked — next to the delivery.</p><p>All numbers in this article come from an actual run. Wherever an expectation
was not confirmed, the text says so.</p><h2 id="four-formats-of-the-same-application">Four Formats of the Same Application</h2><p>The application under examination is a Vaadin Flow application on Java 26,
without Spring, with login, role management, persistent storage, and an audit
log. It has been running for weeks on a Debian server at Hetzner and was
repackaged step by step in the course of an article series. What matters for
this article is not the series but its byproduct:<strong>four delivery formats of
the same application, on the same server, within a few days of each other.</strong></p><h3 id="the-four-formats">The Four Formats</h3><p><strong>WAR on installed Jetty.</strong> The classic model. The servlet container is
standalone software, installed under<code>/opt/jetty</code>, maintained by whoever looks
after the server. The application is an archive that they deploy.</p><p><strong>Thin distribution.</strong> Jetty is no longer an installation but a dependency.
The release is a directory tree: a start script, the application, and next to
it<code>lib/</code> with everything it needs — every dependency as its own file.</p><p><strong>Fat JAR.</strong> The same application, the same Jetty, but everything pushed into a
single archive. 127 files become three.</p><p><strong>jlink distribution.</strong> The thin distribution, extended with a Java runtime
image. The server no longer needs an installed Java; the application brings its
own runtime along.</p><h3 id="what-shifts-in-the-process">What Shifts in the Process</h3><p>Across the four formats, a boundary migrates. In the first format, the
application is the archive and everything else is environment. In the last, the
Java runtime belongs to the release. What someone else used to be responsible
for is now the responsibility of whoever ships.</p><p>For a bill of materials, this is no side issue. It is supposed to describe what
is shipped — and what is shipped is a different scope in the fourth format than
in the first.</p><table><thead><tr><th>Format</th><th>What belongs to the application</th><th>What the environment provides</th></tr></thead><tbody><tr><td>WAR</td><td>the archive</td><td>server, runtime, operating system</td></tr><tr><td>Thin distribution</td><td>application and server</td><td>runtime, operating system</td></tr><tr><td>Fat JAR</td><td>application and server, in one file</td><td>runtime, operating system</td></tr><tr><td>jlink</td><td>application, server, runtime</td><td>operating system</td></tr></tbody></table><h3 id="why-this-setup-holds">Why This Setup Holds</h3><p>Comparing the bills of materials of different projects means comparing, above
all, their dependencies. Here, the application is identical, the source code
the same, the server the same. The only thing that differs is the packaging.</p><p>This makes a question answerable that otherwise remains vague:<strong>How much of
what a bill of materials states depends on the delivery format?</strong></p><p>The answer is in the next chapter, and it turns out differently than you would
expect.</p><h2 id="two-packagings-one-sbom">Two Packagings, One SBOM</h2><p>The thin distribution and the fat JAR differ as much as two deliveries of the
same application can differ. One is a directory tree of 130 files in which
every dependency sits on its own. The other is an archive with 17,904 entries
in which no dependency is recognizable as a distinct piece anymore.</p><p>You would expect this difference to show up in the bill of materials. It does
not.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">code</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><pre tabindex="0"><code>mvn -Pproduction,thin cyclonedx:makeAggregateBom → 129 components
mvn -Pproduction,fatjar cyclonedx:makeAggregateBom → 129 components
only in thin: [] only in fat: [] identical: True</code></pre></div><p>Not similar, not largely congruent:<strong>the same document.</strong> No component appears
only in one list, none only in the other.</p><figure><img src="/images/2026/09/four-delivery-formats-one-sbom-sbom-verpackungen.png" alt="Two packagings of the same application, both with an identical bill of materials" loading="lazy" decoding="async"/><p><em>Figure 1: 130 files next to 3 — and below them, twice the same document.</em></p><table><thead><tr><th/><th>Thin distribution</th><th>Fat JAR</th></tr></thead><tbody><tr><td>Files in the release</td><td>130</td><td>3</td></tr><tr><td>Archives</td><td>127 individual</td><td>1 with 17,904 entries</td></tr><tr><td>Production bundle</td><td>142 entries under<code>META-INF/VAADIN</code></td><td>the same</td></tr><tr><td>SBOM</td><td>129 components</td><td>129 components</td></tr></tbody></table><h3 id="why-it-has-to-be-this-way">Why It Has to Be This Way</h3><p>The reason is not carelessness on the tool&rsquo;s part but the way it works. The<code>cyclonedx-maven-plugin</code> reads what Maven resolved: the project&rsquo;s dependency
graph. This graph is identical in both cases, because both formats arise from
the same project with the same dependencies. Only afterwards does a profile
decide whether the result is packaged as a directory or as an archive — and the
plugin does not see that step.</p><div class="pull-quote"><p><strong>The SBOM describes the dependency graph, not the delivery.</strong></p></div><p>This sentence carries the entire article, and it has an unpleasant consequence.</p><h3 id="what-follows-from-that">What Follows From That</h3><p>An SBOM alone does not say<strong>what</strong> was shipped. It says what a build process
resolved — at some point, somewhere, on a machine nobody can question anymore.
Without a link to the artifact, it is a claim about that build process.</p><p>Establishing that link is not hard, but it is often skipped: a checksum of the
artifact in the document, or an attestation that binds the two together with a
signature. Only then does &ldquo;these components were resolved&rdquo; become the
verifiable statement &ldquo;these components are inside exactly this file.&rdquo;</p><p>If it is missing, the bill of materials remains a document next to the delivery
instead of a document about it. This is not a theoretical objection: chapter 6
will show that one format can be checked file by file against its bill of
materials — and the other not at all.</p><h2 id="three-gaps-on-three-levels">Three Gaps on Three Levels</h2><p>Chapter 3 showed that the packaging does not change the bill of materials. This
chapter shows the other half: what is missing from it, and why it is missing.</p><p>Three parts of the delivery do not appear. They sit on three different levels,
and all three have the same cause.</p><h3 id="the-server">The Server</h3><p>As long as the servlet container is installed software, it is not in the bill
of materials. It came as an archive from the project&rsquo;s website, was unpacked to<code>/opt</code>, and was never obtained through dependency management — 53 MB that
nobody declared.</p><p>As soon as the same server becomes a dependency, it appears:<strong>28 components</strong>,<code>scope=required</code>. Not because anything changed about the shipped software, but
because it now passes through the tool that generates the bill of
materials.</p><h3 id="the-runtime">The Runtime</h3><p>If an application brings its own Java runtime along, the boundary shifts a
second time — and the bill of materials does not follow. A runtime image made
of 16 platform modules is a component with a version and an origin, and it sits
in the release. It is not in the document.</p><p>The reason is plainer than it seems: the image does not yet exist at the build
time of the Maven run. It is created afterwards, by a different tool.</p><h3 id="the-frontend">The Frontend</h3><p>The third gap is the largest, and it always exists. A Vaadin application ships
a frontend bundle built from npm packages. Of these, the Maven SBOM lists<strong>zero</strong> — against<strong>88 actually resolved packages</strong>.</p><p>These are not marginal parts. It is the part of the application that is
executed in the user&rsquo;s browser.</p><h3 id="one-cause-for-all-three">One Cause for All Three</h3><table><thead><tr><th>Part</th><th>in the Maven SBOM</th></tr></thead><tbody><tr><td>Server, installed</td><td>missing entirely — never obtained through Maven</td></tr><tr><td>Server, embedded</td><td>28 components,<code>scope=required</code></td></tr><tr><td>Runtime from<code>jlink</code></td><td>missing — does not yet exist at build time</td></tr><tr><td>Frontend</td><td>always missing — 0 of 88 npm packages</td></tr></tbody></table><figure><img src="/images/2026/09/four-delivery-formats-one-sbom-sbom-luecken.png" alt="Four levels of the delivery and what of them appears in the Maven SBOM" loading="lazy" decoding="async"/><p><em>Figure 2: What the generator sees — and what lies outside its field of view.</em></p><p>Three rows, three levels, one sentence:</p><div class="pull-quote"><p><strong>An SBOM covers what its generator can see. Maven sees Maven.</strong></p></div><p>This is not an indictment of the tool. It does exactly what it was built for,
and it does so reliably. The mistake lies in the expectation that a generator
could issue a bill of materials for something outside its field of
view.</p><p>From this follows the work order for the next chapter: whoever wants a complete
bill of materials needs, for each level, a generator that sees it — and then
one document instead of three. The first of these gaps can be closed, and it is
the largest.</p><h2 id="making-the-sbom-complete">Making the SBOM Complete</h2><p>Of the three gaps from the previous chapter, one can be closed, and it is the
largest: the frontend. The path there is predictable — a second generator that
sees what Maven does not — and it leads through four stumbling points, the
first of which is the most unpleasant.</p><h3 id="two-generators-one-document">Two Generators, One Document</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">code</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><pre tabindex="0"><code>cyclonedx-maven-plugin 2.9.3 → 129 components
@cyclonedx/cyclonedx-npm 6.0.1 → 88 components
-------------------------------------------------------
Overlap of purls : 0
merged : 218 components, 219 edges</code></pre></div><p>The overlap is zero, and that is no coincidence: the two generators describe
separate worlds. Merged, they yield a document in which the Maven share is
59 percent — the other half of the application was previously invisible.</p><p>The merging is handled by a custom script, not an off-the-shelf tool.<code>cyclonedx-cli</code> would be the obvious choice, but it is an executable from a
foreign language ecosystem that wants to be installed before every build. The
custom tool is shorter than the instructions for installing it, refuses to work
on mismatched spec versions and colliding identifiers — and derives the serial
number from the inputs instead of rolling dice for it.</p><h3 id="stumbling-point-1-without-a-lock-file-nothing-is-reproducible">Stumbling Point 1: Without a Lock File, Nothing Is Reproducible</h3><p>The second generator reads the installed package tree. This tree is created
anew on every build from a manifest file that names only version ranges. Two
build runs can thus describe different software without anything having changed
in the project.</p><p>This is not a theoretical worry. Measured on the same application, without a
single changed line of application code:</p><table><thead><tr><th/><th>without lock file</th><th>with lock file</th></tr></thead><tbody><tr><td>Packages in the installed tree</td><td>529</td><td>500</td></tr><tr><td>npm components in the SBOM</td><td><strong>118</strong></td><td><strong>88</strong></td></tr><tr><td>Document total</td><td>248</td><td>218</td></tr></tbody></table><p>Thirty components of difference. The organically grown working tree carried
along packages the project had long since stopped declaring — the package
manager does not reliably clear away such leftovers. The higher number was not
the more complete one, but the contaminated one.</p><p>With the lock file under version control, the finding is stable: two
independent build runs from a fresh checkout both deliver 88 npm components,
218 in total, 219 edges, 500 packages in the tree. The document deliberately
carries<strong>no timestamp</strong> and a serial number derived from the inputs — only
that makes two runs comparable at all.</p><div class="pull-quote"><p>A bill of materials that cannot be reproduced proves nothing. It
describes a state that nobody can restore.</p></div><h3 id="stumbling-point-2-the-order-in-the-build-process">Stumbling Point 2: The Order in the Build Process</h3><p>The merge step does not belong where you would first suspect. The root of the
multi-module project is built<strong>first</strong> — the frontend does not exist there
yet. The step therefore sits in the module that produces the delivery, and runs
before packaging.</p><h3 id="stumbling-point-3-the-nameless-root">Stumbling Point 3: The Nameless Root</h3><p>The npm generator prepends a root node to the tree — the project itself, which
here is called<code>no-name</code> because the manifest file is generated, not
maintained. Adopt it unexamined, and a component without a license migrates
into the document. The evaluation promptly reported it as<code>UNLICENSED</code>; the
local check had found nothing. The node is removed during merging.</p><h3 id="stumbling-point-4-the-bundle-is-not-the-tree">Stumbling Point 4: The Bundle Is Not the Tree</h3><p>The fourth is the most fundamental, and it leads directly into the next
chapter. The generator reads the dependency<strong>tree</strong> — what ships is a<strong>bundle</strong> that a tool ties together from it, removing everything nobody calls.</p><p>How far that goes is shown by the same before-and-after comparison. The
organically grown tree contained nine packages of a commercially licensed
component collection, among them a charting library under its own license. All
nine appeared in the document. In the shipped bundle, there was<strong>no trace</strong> of
them — only styling variables that the base design ships for all components
anyway.</p><p>With the lock file, they are gone entirely, and the reason is sobering: they
had long since ceased to belong to the project. On the Java side, they had been
removed earlier; in the package tree, they lingered because the package manager
had not cleared them away.</p><div class="pull-quote"><p>The document named nine components that were neither shipped nor even
part of the project anymore — among them one under a commercial
license.</p></div><p>So the SBOM does not only undercount, it also overstates — and in the worst
case in both directions at once. Both follow from the same sentence from
chapter 3: it describes the dependency graph, not the delivery. A graph that is
not maintained eventually no longer describes even the
project.</p><h2 id="verifiability-holding-the-sbom-against-the-artifact">Verifiability: Holding the SBOM Against the Artifact</h2><p>The document is now complete enough to allow a question that would have been
pointless before:<strong>Is it correct?</strong></p><p>The question is not whether it is formally valid. The question is whether what
it states matches what is actually shipped. And here the packaging does make a
difference after all — not in the content of the bill of materials, but in the
ability to verify it.</p><h3 id="the-thin-distribution-file-by-file">The Thin Distribution: File by File</h3><p>The directory tree contains 127 individual archives. Each carries a name, each
can be held against the local package repository it came from.</p><p><strong>126 of the 127 are byte-identical.</strong> The one that differs is the application
itself, which is rebuilt on every build. Every third-party component in the
release can thus be matched to a line in the document, and vice versa.</p><p>That is the ideal case: the bill of materials is not just a claim about a build
process but can be checked against the result.</p><h3 id="the-fat-jar-not-at-all">The Fat JAR: Not at All</h3><p>The same release as a single archive:<strong>17,904 entries</strong>, all on one level,
none with an indication of origin. From the file alone, it is no longer
possible to tell which component an entry came from — the boundaries between
the dependencies disappeared during packaging.</p><p>You can check whether a class is present. You cannot check whether it came from
the version the document names.</p><table><thead><tr><th>Format</th><th>verifiable against the artifact?</th></tr></thead><tbody><tr><td>WAR on installed server</td><td>partially — the library directory can be listed, the server is missing from the document</td></tr><tr><td>Thin distribution</td><td><strong>yes</strong> — 126 of 127 archives byte-identical</td></tr><tr><td>Fat JAR</td><td><strong>no</strong> — 17,904 entries without origin</td></tr><tr><td>jlink distribution</td><td>application part like thin, the runtime modules not at all</td></tr></tbody></table><h3 id="the-parallel-that-holds-everything-together">The Parallel That Holds Everything Together</h3><p>Exactly the same thing happens one level further down, and there hardly anyone
notices it:</p><div class="pull-quote"><p><strong>The frontend bundle is the browser&rsquo;s fat JAR.</strong></p></div><p>There, too, hundreds of individual packages are fused into a few files. There,
too, the boundaries disappear in the process. There, too, it is impossible to
say afterwards which package contributed which line — and, as chapter 5 has
shown, some packages do not end up in the result at all.</p><p>Both fusions have the same good reason: fewer files, faster delivery. And both
have the same price.</p><h3 id="what-this-means-in-practice">What This Means in Practice</h3><p>The price is not that you would have to forgo one format or the other. It is
that verifiability has to be established<strong>at build time</strong>, because it cannot
be established afterwards.</p><p>Two means suffice for that, and both are cheap:</p><p><strong>A checksum of the artifact in the document.</strong> This records which file the
bill of materials speaks about — not some result of some run.</p><p><strong>An attestation that binds the two together with a signature.</strong> This
additionally records who is responsible for the build process.</p><p>Without this link, even the most complete bill of materials remains a document
next to the delivery. For the thin distribution, the comparison can be caught
up on if need be; for the fat JAR and for the frontend bundle, the moment is
irretrievably gone.</p><h2 id="putting-the-sbom-into-the-release--and-into-the-platform">Putting the SBOM into the Release — and into the Platform</h2><p>The document is complete, reproducible, and verifiable against the artifact. It
sits in the build directory.</p><p>There it answers not a single question. The asking happens not on the machine
where the build ran, but on the one where the application runs — weeks later,
by someone who never saw the build process.</p><h3 id="the-first-place-next-to-the-delivery">The First Place: Next to the Delivery</h3><p>The bill of materials belongs in the release, for the same reason a package
insert belongs in the package and not in the manufacturer&rsquo;s archive.</p><p>The objection is obvious: this makes the delivery larger. How much larger can
be calculated:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">code</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><pre tabindex="0"><code>sbom.json, uncompressed : 511,851 B
sbom.json, inside archive : 75,841 B
Thin Distribution total : 25,935,935 B
----------------
Share : 2.9 per mille</code></pre></div><p><strong>Three per mille.</strong> That settles the objection — it was never an argument, but
an assumption that nobody had bothered to calculate.</p><p>That the document sits in both delivery formats is no coincidence: the step
that generates it runs before packaging, and both packagings pick it up.</p><h3 id="the-second-place-where-the-searching-happens">The Second Place: Where the Searching Happens</h3><p>A document next to the artifact helps whoever has the artifact. It does not
help whoever wants to know<strong>which</strong> of the shipped versions contained a
particular component — a question that spans multiple releases and that no
single release answers.</p><p>For that, you need a place that holds versions side by side. The submission
sensibly happens<strong>from within the build process</strong>, not through a user
interface — what is generated automatically should also be filed
automatically:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">code</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><pre tabindex="0"><code>POST /api/v1/boms/inventory/bom/upload
multipart: file=@sbom.json
params={"inventorydisplayname": "…", "inventoryversion": "…"}
201 validation_status: valid sbom_format: cyclonedx spec_version: 1.6
total_components: 218</code></pre></div><h3 id="what-the-confirmation-is-worth">What the Confirmation Is Worth</h3><p>The number<strong>218</strong> already appeared earlier in this article — calculated on my
own, from two merged documents. Now it stands in a second, independent place
that has read the document in and found it valid.</p><p>That is the real gain: a private calculation becomes a traceable finding. And
the cross-check comes for free — had the merging from chapter 5 produced a
formally broken document, it would have surfaced here instead of at the
recipient&rsquo;s.</p><h3 id="two-versions-side-by-side">Two Versions Side by Side</h3><p>The version with the lock file was submitted. The previous one remained, and
only that makes visible what the work of the last chapters achieved:</p><table><thead><tr><th/><th>before</th><th>after</th></tr></thead><tbody><tr><td>Components</td><td>248</td><td><strong>218</strong></td></tr><tr><td>Vulnerabilities</td><td>1</td><td><strong>0</strong></td></tr><tr><td>Distinct licenses</td><td>20</td><td><strong>12</strong></td></tr></tbody></table><p>None of these three numbers dropped because less is being shipped. They dropped
because the document stopped reporting things that no longer belong to the
project.</p><p>With that, the inventory is recorded — complete, reproducible, next to the
artifact, and confirmed in a second place. What can be answered with it is the
subject of part 2.</p><h2 id="what-follows-from-all-this">What Follows From All This</h2><p>Four formats, one application, one document. What remains of this for the
decision that stands at the beginning of a project?</p><h3 id="the-trade-off-on-the-bill-of-materials-axis">The Trade-Off on the Bill-of-Materials Axis</h3><table><thead><tr><th>Format</th><th>Completeness</th><th>Verifiability</th><th>What is missing</th></tr></thead><tbody><tr><td>WAR on installed server</td><td>low</td><td>partial</td><td>the server, 53 MB, declared by nobody</td></tr><tr><td>Thin distribution</td><td>high</td><td><strong>complete</strong></td><td>—</td></tr><tr><td>Fat JAR</td><td>high</td><td><strong>none</strong></td><td>the boundaries between the components</td></tr><tr><td>jlink distribution</td><td>high</td><td>like thin</td><td>the runtime, 16 modules</td></tr></tbody></table><p>The column that surprises is the middle one.<strong>Completeness and verifiability
are two different properties</strong>, and the packaging decides only the second. The
fat JAR and the thin distribution carry the same bill of materials; only for
one of the two can it be held against the result.</p><p>Whoever has the choice and values demonstrability takes the thin distribution.
Whoever prefers the fat JAR for good reasons — one file, one step — has to
establish the link between document and artifact at build time, because it
cannot be established later.</p><h3 id="three-sentences-that-carry-this-article">Three Sentences That Carry This Article</h3><p><strong>The SBOM describes the dependency graph, not the delivery.</strong> Two artifacts
whose file counts differ by a factor of 43 produce the same document.</p><p><strong>An SBOM covers what its generator can see.</strong> Three gaps on three levels, one
cause. The largest of them — the frontend, half the application — can be closed
with a second generator.</p><p><strong>A bill of materials that cannot be reproduced proves nothing.</strong> Without a
lock file, two runs of the same project described 118 and 88 components. The
higher number was not the more complete one, but the contaminated one.</p><h3 id="what-this-part-has-not-answered">What This Part Has Not Answered</h3><p>The inventory is recorded, complete enough, and reproducible. With that, the
groundwork is finished — and not a single one of the questions it is collected
for has been answered.</p><p>Whether one of the 218 components carries a known vulnerability is not in it.
Whether one of the licenses conflicts with the planned distribution is not in
it either. Where the code comes from, least of all.</p><p>A bill of materials is a list. It only becomes an answer when someone queries
it — and that is the subject of part 2.</p>
]]></content:encoded><category>Java</category><category>Security</category><media:content url="https://svenruppert.com/images/2026/09/four-delivery-formats-one-sbom-hero.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/09/four-delivery-formats-one-sbom-hero.png"/><enclosure url="https://svenruppert.com/images/2026/09/four-delivery-formats-one-sbom-hero.png" type="image/jpeg" length="0"/></item></channel></rss>