skip to content

How do you add arbitrary custom manifest headers, and what happens when they collide with generated or hand-written ones?

level: seniorimportance: should knowfreq 28%

answer

  1. three sources: manifest, manifestFile, manifestEntries
  2. child tag name = header name
  3. manifestEntries overrides generated defaults
  4. ${} property interpolation
  5. reproducible builds vs Build-Time timestamp

basics

~10 s

Add free-form headers under <archive><manifestEntries>, each child element name becoming a header. You can also merge a hand-written file via <manifestFile>. manifestEntries values take precedence over generated defaults.

solid answer

~40 s

The maven-jar-plugin builds the manifest from three sources under <archive>: (1) <manifest> generated headers (mainClass, addClasspath, addDefault*Entries); (2) <manifestFile>, a hand-authored partial manifest you point to; and (3) <manifestEntries>, free-form headers where each XML child's tag name becomes the header name and its text the value — great for ${...} property interpolation like Git SHA or build timestamp. When the same header is produced by more than one source, the explicitly configured value wins: manifestEntries overrides what the generated <manifest> section or <manifestFile> would set, letting you customize a single header without disabling the defaults. Values support Maven property substitution, so you commonly inject ${project.version}, ${maven.build.timestamp}, or a buildnumber-maven-plugin ${buildNumber}. The same <archive> model is reused by assembly, shade, and war plugins.

code

xml · 7 lines
xml
<archive>
  <manifestEntries>
    <Build-Time>${maven.build.timestamp}</Build-Time>
    <Git-Commit>${buildNumber}</Git-Commit>
    <X-Compile-JDK>${maven.compiler.release}</X-Compile-JDK>
  </manifestEntries>
</archive>

go deeper

for a junior

Know <manifestEntries> lets you add custom Name: value headers.

for a middle

Know the child-tag-name-becomes-header rule and property interpolation.

for a senior

Know precedence (manifestEntries overrides defaults) and the reproducible-build timestamp pitfall.

for a principal

Standardize provenance headers across all artifact types via parent pluginManagement and enforce reproducible-build settings org-wide.

## The three manifest sources The `<archive>` configuration (shared by maven-jar, war, assembly, shade) assembles the manifest from: 1. **`<manifest>`** — *generated* headers controlled by booleans/values: `<mainClass>`, `<addClasspath>`, `<classpathPrefix>`, `<addDefaultImplementationEntries>`, `<addDefaultSpecificationEntries>`. 2. **`<manifestFile>`** — path to a partial, hand-written `MANIFEST.MF` whose headers are merged in. Useful for verbatim OSGi or large header blocks. 3. **`<manifestEntries>`** — free-form: each child element's **tag name is the header name**, its body the value. ```xml <archive> <manifest> <mainClass>com.example.App</mainClass> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> <manifestEntries> <Implementation-Version>${project.version}-${buildNumber}</Implementation-Version> <Build-Time>${maven.build.timestamp}</Build-Time> <X-Compile-Target-JDK>${maven.compiler.release}</X-Compile-Target-JDK> </manifestEntries> </archive> ``` ## Precedence on collisions When the same header could come from multiple sources, the rule is: **explicit `<manifestEntries>` wins**, overriding both the generated `<manifest>` defaults and `<manifestFile>`. In the example above, even though `addDefaultImplementationEntries` would write `Implementation-Version: ${project.version}`, the `<manifestEntries>` override replaces it with the version + build number. This is the idiomatic way to *augment* a default header rather than turning the whole default block off. ## Property interpolation Values are run through Maven's property resolution, so you can inject: - `${project.version}`, `${project.artifactId}` - `${maven.build.timestamp}` (format via `<maven.build.timestamp.format>`) - `${buildNumber}` from **buildnumber-maven-plugin** (Git/SVN revision) - any custom `<properties>` Guard against non-reproducible builds: a wall-clock `Build-Time` breaks byte-for-byte reproducibility; for reproducible builds set `project.build.outputTimestamp` instead. ## Naming caveats - Header names must be valid manifest tokens (alphanumerics and `-`). XML tag names with characters like `:` aren't legal, so a custom prefix is usually `X-`. - Long values are wrapped at 72 bytes per the manifest spec automatically. ## Reuse across plugins Because shade/assembly/war reuse the same `<archive>` element, define the convention once (often in a parent pom's `<pluginManagement>`) so every artifact type carries identical provenance headers.

  • If <addDefaultImplementationEntries> writes Implementation-Version AND you set it in <manifestEntries>, which wins?
    The explicit <manifestEntries> value wins and overrides the generated default.
  • How would you embed the Git commit hash into the manifest?
    Run buildnumber-maven-plugin (or git-commit-id-plugin) to expose a property like ${buildNumber}, then reference it inside a <manifestEntries> child element.
  • Why can a Build-Time header hurt reproducible builds, and what's the fix?
    A wall-clock timestamp differs every build, breaking byte-for-byte reproducibility; use project.build.outputTimestamp (a fixed source-controlled time) instead.

saying these in an interview costs you the question

  • Thinking generated defaults override your explicit manifestEntries (it's the opposite)
  • Using illegal characters like ':' in a custom header/XML tag name
  • Adding a wall-clock timestamp without realizing it breaks reproducible builds
  • Assuming only maven-jar-plugin supports <archive> (shade/war/assembly do too)

context