java -jar can't find included .jar file - java

I'm tearing what little hair I have left over this one: any help will be extremely gratefully received!
I have constructed a .jar file (called, rather uninspiringly, compare-yaml.jar) with the following contents
META-INF/
META-INF/MANIFEST.MF
YamlParser.class
snakeyaml-1.28.jar
MANIFEST.MF contains the following:
Manifest-Version: 1.0
Main-Class: YamlParser
Class-Path: snakeyaml-1.28.jar
Created-By: 11.0.12 (Oracle Corporation)
The Java class compares two Yaml files, although that is incidental.
When I run the .jar file with the command
java -jar compare-yaml.jar a.yaml b.yaml
I get the output
Exception in thread "main" java.lang.NoClassDefFoundError: org/yaml/snakeyaml/Yaml
at YamlParser.readFileIntoMap(YamlParser.java:29)
at YamlParser.main(YamlParser.java:58)
Caused by: java.lang.ClassNotFoundException: org.yaml.snakeyaml.Yaml
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 2 more
However, if I unpack the jar so that snakeyaml-1.28.jar is on the same directory as compare-yaml.jar, the program runs correctly.
So why isn't the jar command picking up snakeyaml-1.28.jar on the classpath when it's in the containing jar?
TIA,
Graeme

The classpath isn't actually recursive: It only checks the top level of classes. Jar-in-jar dependencies aren't supported by java itself.
There's a nice plugin for gradle called shadowJar, that bundles all dependencies automatically into the final jar, maybe that'll help you out here.
Other than that, you'll have to manually bundle dependency jars in another folder, and include them into the classpath at runtime.

Related

how to add online jars to manifest classpath

My idea is to create a jar file with classes and manifest with jar dependencies over the network.
Now for this, I created a small java class and added some libs like Lombok and slf4j API, while building the classes I got the class files and created a manual manifest file as below:
Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Main-Class: packagemain.MainClass
Class-Path: https://repo1.maven.org/maven2/org/projectlombok/lombok/1.18.8/lombok-1.18.8.jar https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/1.7.21/slf4j-simple-1.7.21.jar https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar
I added the jar links online directly to classpath in manifest and I packed these files into a jar by using the command:
jar cfvm myJar.jar MANIFEST.MF *
Now while running I am still getting the error as below:
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
at packagemain.MainClass.(MainClass.java:8)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more
Can anyone help me and let me know what am i doing wrong.

How to run java project from command line (works fine in eclipse, gradle)

I'v got a simple java program. I'm developing in Eclipse, using gradle, and I'm logging with log4j2. This all works fine.
When I come to run from the command line, I do a gradlew build which works, but when I run the jar I get an error:
java -jar build\libs\testproj.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager
at com.xxxxxx.practice.App.<clinit>(App.java:17)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.LogManager
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 1 more
I can see a bunch of log4j files in my userprofile.gradle folder, and I could run a gradle dist task and then got and unzip the resulting zip file and then point at the contents of that, but if there some way of avoiding this? Surely there's something I can add to the manifest or something so that I can run from the command line at will with no overhead?
To be clear, it's just the logging that has this problem. Other than that it runs, the tests work etc from the command line, and the logging works fine from eclipse.
Someplace in your eclipse project, you'll find a library (these end in .jar) named log4j.jar or org.apache.logging-log4j.jar or log4j-api.jar or something along those lines.
This jar contains the classes that are missing when you run this on the command line. Try it: jar tvf log4j.jar prints the contents of the jar and you'll find it contains, for example, org/apache/logging/log4j/LogManager.class.
During compilation and during running a java app, this class needs to be on the classpath. Eclipse is taking care of the compilation part of it.
If you run java as java com.foo.ClassName, the classpath is defined by the -cp parameter: java -cp testproj.jar:log4j.jar com.foo.yourapp.TestApp would work (use semicolon on windows instead). If you don't specify it, the system environment variable CLASSPATH is used, but you don't want that (you can run multiple java apps on one machine after all, so a global setting makes little sense).
If you run java using java -jar myjar.jar however, the classpath is solely taken from the jar file itself; inside the jar file is a file called META-INF/MANIFEST.MF and it contains (in text) key/value pairs. The relevant one is the Class-Path: foo.jar bar.jar entry: Space-separated listings of jar files, relative to the dir your jar is in.
You cannot use the -cp option when using the -jar option; the -cp option is ignored.
So, next steps:
I don't know if gradle made the Class-Path entry correctly. Best option is to use the jar or zip tool to unpack your jar to check that MANIFEST.MF file and see what's there. If there is, say, Class-Path: lib/log4j.jar in that file, then make sure that if testapp.jar is at /Users/Dev123456/project/testapp.jar, that that log4j jar is at /Users/Dev123456/project/lib/log4j.jar. If there is no class-path entry, then the error lies in your gradle config; you'd have to post your build.gradle file in that case.
There are 'striper plugins' which mix all the various jars together in one giant jar. This is usually a bad idea (it just makes the jar itself humongous, and makes builds take longer. If you're deploying java code to a server, you can manage the Class-Path entry by yourself, and if you are making a desktop app, you need an installer which... can manage the class-path stuff just as well. Really no reason to use stripers). I advise against using these.
Just run with java -cp yourapp.jar:dep1.jar:dep2.jar com.foo.fullyqualified.ClassName instead.
Try to run your jar by specifying Log4j jar as well in classpath and then mention your MainClass from testproj.jar :
java -cp build\libs\testproj.jar:build\libs\log4j.jar com.package.MainClass

jar throwing NoClassDefFoundError

I've got a project that is structured liked so:
root/
-- lib/
---- commons-cli-1.2.jar
-- src/
---- my/package/name/*.java
-- bin/
---- my/package/name/*.class
-- .classpath
-- .project
-- manifest.mf
The *.class files in bin/ are made by Eclipse each build. My .classpath has the lib/ included and compiles just fine; it also runs as a "Java Application" just fine with my current stubs.
The issue comes about when I try to create a JAR and include the lib/ dependencies. From the command line I've been issuing:
jar cvfm prog.jar manifest.mf -C bin/ .
The program builds, and then when I try to run java -jar prog.jar, I get:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/cli/Option... (there is more, but I cant copy from the other machine)
My manifest.mf looks like:
Manifest-Version: 1.0
Main-Class: my.package.Main
Class-Path: ./lib/commons-cli-1.2.jar
Seems to me that the Class-Path isn't being included and the JAR cannot find the classes contained in the commons-cli-1.2.jar. I've isolated this to JAR creation, since I can run the compiled classes with no issue.
What am I doing wrong when creating the JAR and including the lib/*.jar?
I have had issues with specifying classpath in the manifest file before. If I were you, I would skip referencing the required libraries in the manifest file and instead include them directly inside your jar. Eclipse allows you to easily do this link
You probably assume that the build process should pull the commons JAR into the new that holds your classes. Wrong assumption.
The default Java approach is that you only include your classes in your jar. But instruct your users that they need to have the commons jar in their class path as well!

Java: NoClassDefFoundError: org/json/JSONException

So, I've spent the whole day with this problem.
I'm sure, that I'm using correct classpath.
Also, I have other packages as dependences, and they work perfectly.
I have a class that uses org.json.*
Also there are some other outer packages used in this class.
All this dependences are placed as jar files in my /path/to/libs/.
json-20160212.jar is among them.
I'm compiling my sources with
javac \
-cp "src/:/path/to/libs/json-20160212.jar:/path/to/libs/other.jar:/path/to/libs/another.jar" \
-d dst/ \
src/com/example/Source.java
Compilation goes without issues.
Then, I'm creating jar from my class-files.
Manifest:
Main-Class: com.example.Source
Class-Path: /path/to/libs/json-20160212.jar
/path/to/libs/other.jar
/path/to/libs/another.jar
Command line:
jar cfm output.jar manifest -C dst/ ./com
I'm getting jar with this manifest:
Manifest-Version: 1.0
Class-Path: /path/to/libs/json-20160212.jar /path/to/libs/other.jar /p
ath/to/libs/another.jar
Created-By: 1.7.0_101 (Oracle Corporation)
Main-Class: com.example.Source
As I've understood, this is ok for compiled manifest to have splitted lines.
Now, I'm running my app from command line and get this error:
Exception in thread "Thread-0" java.lang.NoClassDefFoundError: org/json/JSONException
at com.example.Source.run(Source.java:30)
Caused by: java.lang.ClassNotFoundException: org.json.JSONException
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
... 1 more
As I know, that means, that org.json.JSONException was ok at compile time but missing at run time.
But what must I do with this info?
I have that file. It was at its place during compilation and at runtime.
Also there are other dependences, and their jars are also at that place.
If I remove JSON usage from my app, everything is working ok.
So, I can make conclusion, that it is the package org.json itself, that makes the problem.
What must I do, to make it work?
UPDATE
Now, I've made this changes:
My directory structure:
libs/
json-20160212.jar
other.jar
another.jar
src/
com/
example/
Source.java
dst/
Manifest:
Main-Class: com.example.Source
Class-Path: libs/json-20160212.jar
libs/other.jar
libs/another.jar
Compilation:
javac \
-cp "src/:libs/json-20160212.jar:libs/other.jar:libs/another.jar" \
-d dst/ \
src/com/example/Source.java
Jarchiving:
jar cfm dst/output.jar manifest -C dst/ ./com ./libs
I'm getting jar with the structure as excepted:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/Source.class
libs/
libs/json-20160212.jar
libs/other.jar
libs/another.jar
And I'm running it with java -jar dst/output.jar.
Result is the same: java.lang.NoClassDefFoundError: org/json/JSONException
The problem is your runtime classpath. There's no magic with this error. It quite simply means that org.json.JSONException is not on your runtime classpath. Find the jar that has this class in it and put it on your runtime classpath.
Note that the jars / classes needed for runtime are not necessarily the same as those needed for compiling. You quite often need more on your runtime classpath than your compile classpath. If JSONException isn't used explicitly in the code you are compiling, then it won't have to be on your compile classpath. However, if one of the dependencies to your code needs JSONException and it's not on your runtime classpath, you will get a NoClassDefFoundError.
One other issue that can possibly occur is that you have 2 different versions of the json jar on your classpath. Usually the first version of the class on the classpath gets loaded and the other ignored. If the first jar didn't have the version / signature of JSONException you needed in it but the second did, the correct class you would still get ignored, since it was further down on the classpath.
The issue would appear to be that you are not adding the dependent jars to your resultant jar.
I have created a similar test jar, with the following structure (checking using jar tf)...
META-INF/
META-INF/MANIFEST.MF
BeanIt.class
TestBean.class
lib/
lib/opencsv-3.7.jar
lib/commons-lang3-3.4.jar
My manifest...
Main-Class: BeanIt
Class-Path: lib/opencsv-3.7.jar lib/commons-lang3-3.4.jar
In order to create this jar, you need to perform a command something similar to this...
jar cfm App.jar MANIFEST.MF BeanIt.class TestBean.class lib
You can see that I've added my lib folder to the jar and referred to its contents on the classpath in the manifest.
So, you can update your existing lib, like this...
jar uf App.jar path
Where path is the root path of your path/to/lib directory. And it will simply add that to your jar.
You can check your jar first using jar tf, to see what it contains.
If you are still having difficulties getting it to work, then you can look at a "FAT JAR" solution whereby you expand all the internal jars classes and flatten them all out to a single JAR containing all the necessary classes. They use decision mechanisms to deal with class conflicts in different JARs. Tools such as sbt-assembly or OneJar may be what you need here, if you are unable to get your JAR working the way you expect.
So, the solution:
As I've understand, the only way to access the content of the jar files that are inside your jar, is to write your own class loader.
Without it, jar files must be extracted and that extracted content must be included to output.jar.

executing jar from command line with jars inside

I saw running jar from command line.
However my case is different. I put the jars needed for the main class under lib folder. So my jar is like this:
lib
META-INF
VideoFieldsResults.class
in the META-INF I have:
Manifest-Version: 1.0
Main-Class: VideoFieldResults
when I try to run:
java -jar VideoFormat.jar onBa32hgnBg,Lh-BKkLYYBU C:\VideoFormat2.csv
Where the last 2 are arguments I get:
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gdata/util
/ServiceException
Which does exists under one of the jars inside!
How can I run a command line specifying that all the jars under lib folder inside are required for the jar?
By default, Java cannot load jars from within jars. To accomplish this, you would need a classloader that can handle it. For example One-Jar. There are other options as well (depending on how set you are with including jars inside of a jar or if flattening jars is an option), I'd recommend the answer to this other question

Categories