1

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

Ilia Liachin
  • 1,284
  • 2
  • 17
  • 35
  • Check your resultant jar for its contents. Is the JSON jar actually at /path/to/libs/json-20160212.jar inside your jar, for instance? – ManoDestra Apr 26 '16 at 15:18
  • i think you aren't add specific lib in your project. check it and make sure all needed jars all included in the project correctly. – Mohammadreza Khatami Apr 26 '16 at 15:29
  • Going by your jar command above (`jar cfm output.jar manifest -C dst/ ./com`), it looks like you're not adding the library dependencies to your jar. Include path/to/lib as well as your classes and it should work fine after that. – ManoDestra Apr 26 '16 at 15:30

3 Answers3

2

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.

TheEllis
  • 1,666
  • 11
  • 18
1

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.

ManoDestra
  • 6,325
  • 6
  • 26
  • 50
  • I've updated original post. Please have a look at it. – Ilia Liachin Apr 27 '16 at 07:35
  • Is the required class (org.json.JSONException) definitely in your json-20160212.jar? Double check the casing of JSONException too, in case it should be JsonException instead. So long as that class is in your json jar, the json jar is inside the jar, referred to on the classpath via the manifest and referenced correctly from your calling class, then you should be good to go. Perhaps check that you can call a class from one of your other jars too. If that's okay, then it's probably a case sensitive issue or missing class issue. – ManoDestra Apr 27 '16 at 13:17
0

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.

Ilia Liachin
  • 1,284
  • 2
  • 17
  • 35
  • You shouldn't have to do this (as I said previously I already made a JAR containing another jar, referenced correctly and it ran fine), but there are certainly some tools that allow you to do this such as sbt-assembly (I use this one in work) and OneJar. They use decision mechanisms to deal with class conflicts and they expand all the classes from dependent jars into your single fat jar. Updated my answer with some further advice regarding fat jar solutions. – ManoDestra Apr 27 '16 at 16:18
  • 1
    I think, you were able to run your jar because file structure inside your jar is the same as in your project directory. So your libs (dependences-jars) were taken not from the jar, but from your local file system. Try to move your result jar to the independent place and run it. – Ilia Liachin Apr 28 '16 at 10:06
  • Also, please, have a look at the official documentation: http://docs.oracle.com/javase/tutorial/deployment/jar/downman.html. The first Note says: "The Class-Path header points to classes or JAR files on the local network, not JAR files within the JAR file or classes accessible over Internet protocols. To load classes in JAR files within a JAR file into the class path, you must write custom code to load those classes." Anyway, thanks a lot! Your answer gave me the direction for solution. – Ilia Liachin Apr 28 '16 at 10:10
  • Strange. It quite commonly works, despite the documentation. I guess it depends on what's inside the internal JAR. If it requires some form of nested class loading, then it may need custom handling, but for straightforward JARs, it seems to be fine. You can always use the Export as Runnable Jar option in Eclipse. It does all of that for you and handles the class loading aspect. – ManoDestra Apr 28 '16 at 13:03
  • I use IntelliJ IDEA, but there is also the same option. Actually this option extracts libs-jars and packs their contents into the resulting jar. This is very convenient in the case of local development. But in my case I can't rely on IDE. I'm uploading sources to the remote server and sources are being compiled there without IDE. – Ilia Liachin Apr 30 '16 at 06:10
  • But there is one thing I agree with you. It is strange. As I've wrote in the original question, other inner jars work. If I remove json features from my project, other libraries work as they are in class path. – Ilia Liachin Apr 30 '16 at 06:15
  • You can use sbt-assembly or sbt-onejar to build successful fat jars. I use sbt-assembly and it's pretty good. – ManoDestra Apr 30 '16 at 21:10