5

I built my project and generated a JAR file using Gradle build framework. However, the output jar file fails to load the main methods from the main class (miner.Tracker) in this case.

As I mentioned, a run with -jar option failed.

$ java -jar Backtracker.jar
Error: Could not find or load main class miner.Tracker

I also tried to directly run the class with -cp option but it failed.

$ java -cp Backtracker.jar miner.Tracker
Error: Could not find or load main class miner.Tracker

Lastly, I uncompressed the jar file and call the class from inside. This time, it has succeeded to find and run the class with the main method.

$ mkdir classes
$ cd classes
$ classes $ tar xvf ../Backtracker.jar
x META-INF/
x META-INF/MANIFEST.MF
x CHANGELOG.md
x com/
1. ...

classes $ java miner.Tracker
2021-04-12 22:47:48.008 | Logging started
...

Here's the contents of META-INF/MANIFEST.MF file.

Manifest-Version: 1.0
Implementation-Title: BackTracker
Implementation-Version: 1.9.xx
Specification-Title: release
Specification-Version: 1.9.xx
Main-Class: miner.Tracker

And I am running it from Oracle Java 1.8.

$ java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

Thanks for your help.


Update:

$ tar tvf Backtracker.jar |grep miner\/Tracker

-rw-r--r-- 0 0 0  2175 Apr 14 20:38 miner/TrackerUtil.class
-rw-r--r-- 0 0 0 40963 Apr 14 20:38 miner/Tracker.class
kriegaex
  • 63,017
  • 15
  • 111
  • 202
kjee
  • 359
  • 5
  • 19
  • 1
    Your package name is incomplete, can you post the structure of your app? – code_mechanic Apr 16 '21 at 17:20
  • 1
    It would be easier if you shared the jar file. – Olivier Apr 17 '21 at 09:12
  • 1
    Seriously? You are sharing all this information and omit **exactly** what is important behind "...", i.e. where in your directory structure the main class is? With all due respect: Is this some kind of puzzle or quiz? Why are you making it extra hard for everyone to help you? – kriegaex Apr 17 '21 at 16:51
  • @code_mechanic Jar file contains tens of thousands of class files and directory structure is somewhat complex. At least I can share ``` $ tar tvf Backtracker.jar |grep miner\/Tracker -rw-r--r-- 0 0 0 2175 Apr 14 20:38 miner/TrackerUtil.class -rw-r--r-- 0 0 0 40963 Apr 14 20:38 miner/Tracker.class ``` can there be any better way to share directory structure? – kjee Apr 18 '21 at 02:00
  • Yes, there is a better way: Edit the question and then notify people by a new comment. Comments are not the right spot to share logs because formatting and number of characters are limited. I just did that on your behalf, see above. – kriegaex Apr 18 '21 at 03:00
  • To me the file structure looks as expected. Please double-check if you have any strange Unicode characters somewhere in your package or class name (such as invisible spaces, funny dots) or in your manifest. This is just a guess, because you did not post your JAR online. Please also check if accidentally you changed the package name in your Java source file before compilation, i.e. the file might be in the right place but logically in the wrong package. – kriegaex Apr 18 '21 at 03:20
  • `Specification-Version: 1.9.xx`, this should contain numbers only. Try setting version to `1.9.1` https://stackoverflow.com/questions/20994766/jar-manifest-file-difference-between-specification-and-implementation – 11thdimension Apr 18 '21 at 03:22
  • That seems to be irrelevant, I tried with this exact manifest on my workstation. – kriegaex Apr 18 '21 at 03:23
  • We know that culprit is `Manifest.MF` file, as extracted content works. Try building the JAR file by using exploded content using command `jar cvf test.jar *` inside exploded JAR. If the newly produced JAR works with the second command `java -cp ...` then you can start adding other manifest attributes and compare against the one generated by Gradle. Use some HEX editor for comparison – 11thdimension Apr 18 '21 at 03:27
  • Logic implies that the manifest probably is not the problem, otherwise it would work with `java -cp ...`. But your idea is good anyway in order to double-check if something is wrong with how the JAR was packaged. – kriegaex Apr 18 '21 at 03:30
  • As for my suggestion to check with a package name disagreeing with directory structure, I recreated the situation here and Java 9+ should report something like `Reason: java.lang.NoClassDefFoundError: minerxxx/Tracker (wrong name: miner/Tracker)` after the `Could not find or load main class`. But Java 8 does not report a reason, so if you run on Java 8 you might never notice until you check the package name in the source file. But actually, if this would be the problem, it also should not work when unpacked. The only way to do more than speculate is to inspect your real JAR. – kriegaex Apr 18 '21 at 03:33
  • @kjee what is `miner` here? Main package name or sub package name. – code_mechanic Apr 18 '21 at 04:41
  • 1
    *"Jar file contains tens of thousands of class files"* Why don't you make a simple test jar with a single `miner.Tracker` class that just prints "Hello World"? Don't you think it would be easier to troubleshoot? – Olivier Apr 18 '21 at 07:20
  • @kjee, this issue was important to you to put a bounty on it. Why was it not important enough to provide feedback to comments and answers or to accept my answer? Now the bounty period has expired and 200 points were for nothing. I still got 100, but not because you accepted the answer, only because it happens to be the answer with the most upvotes. It is not so much about the points as such, I am just wondering why you are displaying such self-contradictory behaviour. People put effort into their replies, you showed no respect for that. – kriegaex Apr 25 '21 at 00:24

1 Answers1

5

After some experimentation, I agree with user 11thdimension that something might be wrong with your manifest file. I do not think that it is about the non-numeric versions, though. I tried, it works.

What I did notice is that if you add a line which contains more than a normal space (character 32 dec, 20 hex), you are going to see the error you described, simply because the manifest cannot be parsed. Obviously the manifest parser expects something like MyKey: MyValue on each non-empty line. Even empty lines anywhere else than at the end can cause the same problem, if they are in the middle of a so-called section. So if you have manifests like one of the following, you are going to see a ClassNotFoundException, no matter if you use -cp or -jar:

Manifest-Version: 1.0
Main-Class: miner.Tracker
x
Manifest-Version: 1.0

Main-Class: miner.Tracker

Even if at after the last line you have a line containing just a tab character like this, the error occurs:

Manifest-Version: 1.0
Main-Class: miner.Tracker
    

The same is true if you have lines using other invisible characters, such as non-standard Unicode spaces.

Like I said in my previous comment: Check for such characters anywhere in your manifest. If that does not yield any results, please also check your Java source code files, especially package and class names.


Update: Here is a little Java example showcasing what I explained before:

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.jar.Manifest;

public class ManifestValidator {
  public static void main(String[] args) {
    // Valid: nothing unusual
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Tracker\n");
    // Valid: trailing empty line
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Tracker\n\n");
    // Valid: line continuation by leading space
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Track\n er\n");
    // Valid: last line is ignored if it does not end with a line feed
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Tracker\nSome garbage");

    // Invalid: line beginning with tab
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Tracker\n\t\n");
    // Invalid: not a key-value pair "key: value"
    parseManifest("Manifest-Version: 1.0\nMain-Class: miner.Tracker\nFoo=bar\n");
    // Invalid: empty line in section
    parseManifest("Manifest-Version: 1.0\n\nMain-Class: miner.Tracker\n");
    // Invalid: non-default unicode space instead of normal one
    parseManifest("Manifest-Version: 1.0\nMain-Class:\u00A0miner.Tracker\n");
  }

  public static void parseManifest(String content) {
    try {
      Manifest manifest = new Manifest(new ByteArrayInputStream(content.getBytes()));
      System.out.println(manifest.getMainAttributes().entrySet());
    }
    catch (IOException e) {
      System.out.println(e);
    }
  }
}
[Manifest-Version=1.0, Main-Class=miner.Tracker]
[Manifest-Version=1.0, Main-Class=miner.Tracker]
[Manifest-Version=1.0, Main-Class=miner.Tracker]
[Manifest-Version=1.0, Main-Class=miner.Tracker]
java.io.IOException: invalid header field
java.io.IOException: invalid header field
java.io.IOException: invalid manifest format
java.io.IOException: invalid header field
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • 1
    **Update:** I added some sample code parsing valid and invalid manifests using the corresponding JDK class. – kriegaex Apr 18 '21 at 04:59