12

I believe this problem has been asked before on stackoverflow. And I'd like to mention that I tried the solutions that came with the questions related to mine. The one that came the closest to my problem was: Load properties file in JAR?.
Sadly the solution described there didn't work for me. And due the age of the question I thought asking it again is the way to go.

Heading on to describing my problem.
So currently I'm working on a library project which has been setup with maven and creates an extension for the current Spring AMQP project.
The goal here is to supply a JAR file which can be included into another project to support a specific way of communicating over a message broker.
At this point I'm implementing the configuration option to allow users to configure the messaging client to their liking. But while i was testing the functionality of this feature I hit a problem while using the library in an executable JAR.

While running it in the Eclipse workspace everything seems to work just fine. But when I try to run it from my desktop (as a runnable JAR) the properties file does not seem to be found anywhere.

Just to give a quick overview of the workspace/projects setup as described above: workspace overview

The project structure of both project reflects the Maven default one:

- src/main/java
    - java source files
- src/main/resources
    - resource files
- src/test/java
    - java test files
- src/test/resources
    - test resource files

Where the library file contains a default.properties file in the src/main/resources folder and the chatclient project a custom.properties file.

Once the runnable JAR file has been build it has the following structure in it.

- com
- junit
- META-INF
- org
- resources
    - default.resources
    - custom.resources

I believe the resource files should not be located there. but in the META-INF/maven folder instead. After trying out stuff like:

  • Adding a META-INF folder into my src/main/resources folder and putting the property files there.
  • Adding a MANIFEST file with Class-Path: . in it.
  • Loading the file in multiple ways in code.

But nothing seems to work. I guess it is Maven related and a simple change in the pom.xml could fix it. Sadly my knowledge on the Maven project setup and pom related subjects is very basic (this is my first project using maven). And I can't seem to find any documentation on it, even though I know it should be there (probably a problem caused by me).

Before I forget to mention it. I load the property files using this way:

Properties props = new Properties();
prop.load(<custom static class>.class.getResourceAsStream(filename));
return props;

Also the pom.xml for my library looks like:

-- Artifact stuff --
<packaging>jar</packaging>
<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>
-- Dependency stuff --

And the one for the project that uses the library look like:

-- Artifact stuff --
<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

<dependencies>
  <dependency>
    <groupId>com.maxxton</groupId>
    <artifactId>async-amqp-messaging</artifactId>
    <version>0.2</version>
  </dependency>
</dependencies>
-- Other stuff --

I hope there is someone who's a little more advanced on this subject and could help find a solution for this problem. And if you need any additional information on the project files/structure, please let me know. I'd gladly share it with you.

Update (28-04-2015 {1})

For testing I created a sample project which tries to load property files the same way as the scenario described above. Even while following the Maven documentation (Using the META-INF folder) I was not able to load the properties.

For the sake of this question I uploaded the testing workspace here.

I hope someone could help me fix this, as the normal way as described on the Maven website does not seem to work for me.

Update (28-04-2015 {2})

Well I managed to fix a part of the problem. Since I added the configuration for the maven-assembly-plugin (building runnable JAR with deps), I was able to get the correct structure within my JAR file. The thing I added was:

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <finalName>project</finalName>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>com.test.project.Application</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Then when running clean compile assembly:single I managed to get the right structure.

JAR root
    - com
    - META-INF
        - MANIFEST.MF
        - default.properties
        - custom.properties

While this fixes a part of the problem. The file loading still results in a NullPointerException.

Final Update (04-05-2015)

After a long Maven struggle I managed to get everything the way I want it to be. Following the advice given by both @Deepak and @Joop Eggen, I did some research on how to have all the dependencies in a lib folder as jar instead of unpacking them in a 'uber' jar. After trying loads of stuff I stumbled upon this answer. Following the instruction there seems to create this structure:

- runnable.jar
- lib
    - spring-amqp.jar
    - spring-core.jar
    ...

When following @Joop Eggen's advice I managed to get the property loaded the way I want to. So it seems this question has been answered. Currently I'm still figuring out how to award each answer as I'm not able to split the bounty into two pieces. I'll get back on that.

Side Note

Although I awarded both the bounty and the answer to @Joop Eggen does not mean that @Deepak's answer did not contribute. It did give some great information on best practice, but was not as complete as the accepted answer. So please when finding your answer here give him some of the credit too.

Community
  • 1
  • 1
Robin Hermans
  • 1,579
  • 1
  • 24
  • 52
  • 1
    Your JAR structure looks wrong to me. – Tim Biegeleisen Apr 24 '15 at 10:17
  • Yes it is. But I guess in order to fix it I need to change some settings in my Maven pom.xml. After all that defines the way the JAR structure is generated. Or am I wrong on this one? – Robin Hermans Apr 24 '15 at 10:21
  • 1
    Find out what the root of your JAR is pointing to and go from there. My suspicion is that it is _not_ pointing where you think. – Tim Biegeleisen Apr 24 '15 at 10:24
  • Well, looking at my MANIFEST.MF file within the META-INF folder it states that the Class-Path starts at the top of the Jar file. It sets **Class-Path: .** – Robin Hermans Apr 24 '15 at 10:28
  • Are you using Spring framework? – ersnh Apr 28 '15 at 09:47
  • @ns12 I'm only using the Spring AMQP project. I don't use any other dependencies since it is just overhead for my library. If I remember correctly someone told me that there was a Utils class available within spring which makes file loading easier (if that is what you where aiming for?) – Robin Hermans Apr 28 '15 at 09:51
  • 1
    There is a plugin provided by spring-boot which creates a runnable jar. Look @ http://stackoverflow.com/a/29040063/3985566. See if its helpful. – ersnh Apr 29 '15 at 06:43
  • 1
    Alright thanks, I'll look into it. But I'd really like this question not to focus on any framework related solutions. – Robin Hermans Apr 29 '15 at 06:52
  • Is `filename` relative or absolute? Personnally, I always use absolute path. All my resources located under `src/main/resources` and I load them with a string that starts with a `/`. So for example, if you have `src/main/resources/custom.properties`, I load them with `getClass().getResourceAsStream("/custom.properties");`. This always works on both Eclipse and Jar (make sure that `src/main/resources` is on your classpath in Eclipse (normally, the m2e plugin takes care of that, else try 'Maven-->update project') – Guillaume Polet Apr 29 '15 at 11:12
  • Hey man, I dont have time to write an answer, but what Guillaume is saying above is pretty much on the money. Firstly you have a problem with the maven assembly plugin, it's putting your properties file into `META-INF` - if you can use a descriptor file to specify `\<\outputDirectory>` then the properties file will go into the root of your jar and `getClass().getClassLoader().getResourceAsStream("\my.properties")` or the like should work! – vikingsteve Apr 29 '15 at 13:22

2 Answers2

1

The solution to this is simpler, however there are couple of concepts that I would like you to be aware off.

  1. It is not a good practice to create one big jar by using maven assembly plugin. In your examples, you are using small jars both developed by you, so it might seem okay. But as your real projects get bigger, this is not ideal, you would want separation of different modules and not one big jar. The ideal practice is opposite to what you are trying to achieve - you should aim to have smaller and smaller jars. You might in future want the convenience of replacing these smaller jars without having to deliver the entire package again.

  2. You would surely not want to unpack 3PP jars and package them as your own jar!

  3. Based on the above, you might wonder how do you create executable jars. Well, the answer is having the dependent jars in the class-path. When executing the "project" jar, you should have the "library" jar in the class-path and it will be able to find the properties file inside as expected.

  4. You places the properties files in META-INF directory. The right place for them is the resources folder. And if you follow the point No.1, things will work as expected.

KayDK
  • 134
  • 10
  • This seems like a good advise that the dependency jars should be kept separate from the runnable JAR. I'll keep that in mind. The reason why the properties are in the META-INF folder is related to the Maven documentation. It states that resources should be placed there. Maybe they did that to prevent 'trashing' the JAR structure. – Robin Hermans Apr 29 '15 at 12:09
  • 1
    No, as a good practice, the resources should be kept in the resources folder or any folder within the resources itself. Please accept the answer if you found it useful. Thanks. – KayDK Apr 29 '15 at 12:12
  • 1
    Oke thanks. Well the answer is usefull for best practice stuff, though it only gives a hint in the direction of creating a solution. Please have patience as I'd like to see other peoples opinion/solution to this problem. – Robin Hermans Apr 29 '15 at 12:18
1

There are two ways to get resources,

  • with a ClassLoader against the entire class path using absolute paths (without /...),
  • with a class, using a relative (...) or absolute (/...) path inside the jar of that class.

The latter seems more direct, and can be used as:

getClass().getResource("/...");
ClassInJar.class.getResource("/...");

Now getClass() is only possible in a non-static object and is dangerous too: the actual class might be some child, not in the library jar.


On the actual structure of your application. I know a maven directory convention:

src/main/java/...
src/main/resources/...

where /... gets into the jar/war; the package directory.

Reassembling jars is not good. There always is the Class-Path: ... entry in META-INF/MANIFEST.MF. Following the basic maven conventions is best.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • Alright, thanks for sharing your solution with me. There are some overlapping points with @Deepak answer. So that's a good thing I guess. One thing I'd like to know is where you'd place the resources. Would you put them in the META-INF folder or at the base of the JAR file? – Robin Hermans Apr 29 '15 at 12:30
  • Not in META-INF. The root folder is not wrong. META-INF has JVM functionality: not only the manifest, but also class index or Java SPI interface lookup files. – Joop Eggen Apr 29 '15 at 13:20