How to avoid resource collisions in library jars?
dependency-management, java, maven, package
Solution
And what do you do in Java to avoid collisions between libraries? Packages! This is established and well understood approach. Packages work with resources as well:
com/example/foo/Foo.class
com/example/foo/properties.txt
and second library:
com/example/bar/Bar.class
com/example/bar/properties.txt
Note that `properties.txt` lies in different packages and thus directories in final JAR. Actually this approach is preferred because the API for retrieving such resources becomes easier:
App.class.getResourceAsStream("properties.txt"))
vs.
Bar.class.getResourceAsStream("properties.txt"))
It just works because `Class.getResourceAsStream()` is by default local to package of underlying class. Of course when you are inside an instance method of either `Foo` or `Bar` you simply say `getClass().getResourceAsStream("properties.txt")`. Moreover you can still reference both files easily, just like you reference classes:
getClass().getResourceAsStream("/com/example/foo/properties.txt");
getClass().getResourceAsStream("/com/example/bar/properties.txt");
I'm skeptical because I haven't seen any Maven example that actually does this.
Real world example: you have a Spring integration test named `com.example.foo.FooTest`. By default Spring expects the context file to reside under: `/src/test/resources/com/example/foo/FooTest-context.xml`.
Problem
I'm worried about the situation when libraries `Foo` and `Bar` each expose a resource on the classpath with the same name, say `properties.txt` in this example. Assuming a Maven set up and the `jars` are deployed with Maven, if I have this set up: Library Foo: ``` $ cat Foo/src/main/resources/properties.txt $ Foo ``` and Library Bar: ``` $ cat Bar/src/main/resources/properties.txt $ Bar ``` And an `App` that depends on them, whose `pom` looks something like this - in a nutshell this just says "build a jar-with-dependencies, and depend on `Foo` and `Bar`: ``` <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>bundle-project-sources</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass>me.unroll.deptest.App</mainClass> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> <manifestEntries> <Implementation-Build>${buildNumber}</Implementation-Build> </manifestEntries> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> ``` Problem is it really seems like the `properties.txt` file was clobbered. Let's try a `jar tf`: ``` unrollme-dev-dan:target Dan$ jar tf App-1.0-SNAPSHOT-jar-with-dependencies.jar META-INF/ META-INF/MANIFEST.MF properties.txt META-INF/maven/ META-INF/maven/me.unroll.deptest/ META-INF/maven/me.unroll.deptest/Bar/ META-INF/maven/me.unroll.deptest/Bar/pom.xml META-INF/maven/me.unroll.deptest/Bar/pom.properties META-INF/maven/me.unroll.deptest/Foo/ META-INF/maven/me.unroll.deptest/Foo/pom.xml META-INF/maven/me.unroll.deptest/Foo/pom.properties me/ me/unroll/ me/unroll/deptest/ me/unroll/deptest/App.class ``` So I ran a `main` class in `App` that does: ``` try (InputStream is = App.class.getClassLoader().getResourceAsStream("properties.txt")) { java.util.Scanner s = new java.util.Scanner(is); System.out.println("Scanner: " + s.next()); } ``` And the output is: ``` unrollme-dev-dan:target Dan$ java -jar App-1.0-SNAPSHOT-jar-with-dependencies.jar Scanner: Bar ``` Whoops, Bar won. No warnings or errors when `mvn package`-ing `App`. No warnings or errors when running that the wrong file may have been chosen, in fact it failed silently. So I want to ask proper practice to avoid this. One, something like this should fail loudly, not softly. Two, the only solution I can think is that all resource files should be properly packaged like everything else in Java development, i.e., a library should never expose `properties.txt` in the "global" namespace; it should appear in a folder like `me/unroll/deptest/foo` just like everything else. I'm skeptical because I haven't seen any Maven example that actually does this. So what is best practice here?