Why is mergeServiceFiles() needed in a Shadow build, and what breaks if you omit it?
answer
- META-INF/services collisions
- ServiceLoader providers dropped
- concatenate not overwrite
- JDBC driver / SLF4J binding missing
- transformer
basics
~10 sMultiple dependencies can each ship a file at the same path under META-INF/services/. Without mergeServiceFiles() one overwrites the others, so some ServiceLoader providers vanish and features silently break.
solid answer
~40 sJava's `ServiceLoader` SPI discovers implementations via text files named after the service interface under `META-INF/services/` (each lists provider class names). Many libraries ship such files — and several jars can contain a file at the **exact same path**, e.g. `META-INF/services/java.sql.Driver`. When Shadow flattens everything into one jar, two entries at the same path collide; the default copy strategy keeps only one, so providers from the losing jars disappear. `mergeServiceFiles()` is a Shadow **transformer** that detects these `META-INF/services/*` collisions and **concatenates** their provider lists instead of overwriting. Symptoms of omitting it: `ServiceLoader` finds fewer providers than expected — missing JDBC drivers, missing SLF4J bindings, missing Jackson modules, Reactor/Netty providers, etc. The fix is one line inside the `shadowJar` block. Related transformers handle other mergeable formats (e.g. `Log4j2PluginsCacheFileTransformer`, properties, Netty's `META-INF/native-image`).
code
kotlin · 5 linestasks.shadowJar {
mergeServiceFiles() // concat META-INF/services/*
append("META-INF/spring.handlers")
append("META-INF/spring.schemas")
}go deeper
Know that mergeServiceFiles() combines META-INF/services files so providers aren't lost.
Explain ServiceLoader/SPI, the same-path collision, concat vs overwrite, and concrete symptoms like missing JDBC drivers.
Discuss companion transformers (Spring handlers, Log4j2 cache, Groovy extensions) and how to diagnose by unzipping the jar.
Treat resource-merge policy as part of a packaging standard, ensuring SPI/plugin registries survive shading across all services.
## The Java Service Provider Interface Java's `java.util.ServiceLoader` lets a library expose an interface and let *consumers* drop in implementations discovered at runtime. The wiring is a plain text file: ``` META-INF/services/<fully.qualified.ServiceInterface> ``` whose lines are the fully-qualified names of provider classes. `ServiceLoader.load(Foo::class.java)` reads **all** such files visible on the classpath and instantiates the listed providers. Crucially, multiple jars may each ship a file at the *same path* — e.g. three JDBC drivers all provide `META-INF/services/java.sql.Driver`. ## Why a fat jar breaks this A jar (zip) cannot have two entries at the identical path. When Shadow merges jar A and jar B that both contain `META-INF/services/java.sql.Driver`, only one survives. The default behavior is **last-write-wins / first-wins**, silently dropping the other providers. Nothing errors at build time; you discover it at runtime when `ServiceLoader` only finds one driver, or your logging backend isn't picked up. ## The fix: a transformer Shadow's **transformers** post-process entries during the merge. `mergeServiceFiles()` registers a `ServiceFileTransformer` that, for any path under `META-INF/services/`, **concatenates** the line contents of all colliding files into one merged file. ```kotlin tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") { mergeServiceFiles() } ``` or in the modern API simply inside the `shadowJar { }` accessor: ```kotlin shadowJar { mergeServiceFiles() } ``` You can scope it: `mergeServiceFiles("META-INF/services")` or use the configuring overload to include/exclude specific service files. ## Other merge transformers The same collision class affects other resource formats. Common companions: - `mergeGroovyExtensionModules()` — Groovy extension descriptors. - `append("META-INF/spring.handlers")` / `append("META-INF/spring.schemas")` — Spring's namespace handler files. - `Log4j2PluginsCacheFileTransformer` — Log4j2's binary plugin cache. - `transform(...)` with a custom `Transformer` for bespoke formats. ## Mental model Think of `mergeServiceFiles()` as turning an overwrite into an append for SPI registries. Any time discovery is registry-file-based and several jars register, you need a merge transformer or the registry ends up truncated. ## Diagnosing Unzip the produced jar and inspect `META-INF/services/` — if a file you expect to list several providers lists only one, your transformer is missing or mis-scoped.
- Give a concrete runtime symptom of a missing mergeServiceFiles().A JDBC `java.sql.Driver` provider isn't found (`No suitable driver`), or an SLF4J/logging binding or Jackson module silently isn't registered, because only one provider file survived the merge.
- How would you handle Spring's spring.handlers/spring.schemas in a fat jar?Those are line/property files that must be concatenated too — use `append("META-INF/spring.handlers")` and `append("META-INF/spring.schemas")` (Spring Boot's bootJar handles this automatically, but a plain Shadow build does not).
- Does mergeServiceFiles() solve duplicate .class file collisions?No — it only merges service descriptor text files. Class-file duplicates from overlapping dependencies are a separate concern handled by exclusions, relocation, or accepting first-wins.
Two guest lists for the same event get merged into one combined list — without merging, only the last list shown at the door gets in and everyone on the others is turned away.
saying these in an interview costs you the question
- Saying ServiceLoader files are automatically merged by Shadow without the transformer.
- Confusing service-file merging with class duplicate handling.
- Claiming the failure is a build-time error — it's a silent runtime defect.