Fehler: Hauptklasse Main konnte nicht gefunden oder geladen werden Ursache: java.lang.ClassNotFoundException: Main

philanthrop

Bekanntes Mitglied
Ich weiß, die Frage klingt sehr dumm, aber was stimmt hier nicht?

Code:
plugins {
    id 'java'
}

version '1.0'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'

    // https://mvnrepository.com/artifact/javax.mail/mail
    implementation 'javax.mail:mail:1.4.7'

    // https://mvnrepository.com/artifact/org.linguafranca.pwdb/KeePassJava2
    implementation 'org.linguafranca.pwdb:KeePassJava2:2.2.1'
}

test {
    useJUnitPlatform()
}

jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    manifest {
        attributes "Main-Class": "Main"
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

Ich baue die .jar ganz normal mit ./gradlew jar ... die Main.class (und Manifest) ist laut 7zip auch enthalten, umso merkwürdiger der Fehler.
 
Du gibst uns noch viel zu wenig Informationen. Zeige uns z.B. einmal die Main.java. Wie startest Du das Programm?
 
Na gut, hier ist die Main.java:

Java:
import org.linguafranca.pwdb.kdbx.KdbxCreds;
import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase;
import org.linguafranca.pwdb.kdbx.simple.SimpleEntry;

import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import java.awt.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

public class Main {
    private static final TrayIcon trayIcon;

    static {
        //Obtain only one instance of the SystemTray object
        SystemTray tray = SystemTray.getSystemTray();

        //If the icon is a file
        Image image = Toolkit.getDefaultToolkit().createImage("info-solid.png");
        //Alternative (if the icon is on the classpath):
        //Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon.png"));

        trayIcon = new TrayIcon(image, "Tray Demo");
        //Let the system resize the image if needed
        trayIcon.setImageAutoSize(true);
        //Set tooltip text for the tray icon
        trayIcon.setToolTip("System tray icon demo");
        try {
            tray.add(trayIcon);
        } catch (AWTException e) {
            throw new RuntimeException(e);
        }
    }

    record PropsSessionStore(String name, String folderName, Properties properties, Session session, Store store) {
    }

    private static SimpleDatabase getDb() throws IOException {
        KdbxCreds cred = new KdbxCreds("".getBytes());
        InputStream inputStream = new FileInputStream("Database.kdbx");
        return SimpleDatabase.load(cred, inputStream);
    }

    private static PropsSessionStore getPropsSessionStoreGmail() throws MessagingException, IOException {
        String host = "imap.gmail.com";
        SimpleDatabase db = getDb();
        SimpleEntry entry = db.findEntries(host).get(0);
        int port = 993;
        String user = entry.getUsername();
        String pass = entry.getPassword();
        Properties props = new Properties();
        props.put("mail.imap.host", host);
        props.put("mail.imap.port", "993");
        props.put("mail.imap.starttls.enable", "true");
        props.put("mail.imap.ssl.trust", host);
        Session ses = Session.getDefaultInstance(props);
        ses.setDebug(true);
        Store store = ses.getStore("imaps");
        store.connect(host, port, user, pass);
        return new PropsSessionStore(host, "INBOX", props, ses, store);
    }


    private static void displayTray(String caption, String text) {
        trayIcon.displayMessage(caption, text, TrayIcon.MessageType.INFO);
    }

    private static void watch(PropsSessionStore inbox1) {
        StringBuilder lastSubject = new StringBuilder();
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    Folder f = inbox1.store().getFolder(inbox1.folderName());
                    f.open(Folder.READ_ONLY);
                    int count = f.getMessageCount();
                    System.out.println("count = " + count);
                    Message[] messages = f.getMessages(count, count);
                    String subject = messages[0].getSubject();
                    if (!lastSubject.toString().equals(subject)) {
                        lastSubject.delete(0, lastSubject.length());
                        lastSubject.append(subject);
                        displayTray(inbox1.name() + " " + inbox1.folderName() + ":", subject);
                    }
                    f.close(false);
                } catch (MessagingException ex) {
                    System.out.println("ex = " + ex);
                    System.exit(0);
                }
            }
        }, 0, 1000 * 60);
    }

    public static void main(String[] args) throws MessagingException, InterruptedException, IOException {
        watch(getPropsSessionStoreGmail());
        Thread.sleep(1000 * 10);
    }
}

Ich starte mit: java -jar build/libs/MailNoti-1.0.jar

Noch seltsamer ist, dass ich in IJ auf Run klicken kann, und es dort funktioniert.
 
Ok, das sieht so weit erst einmal relativ dubios aus.

Kannst Du uns noch den ganzen Stacktrace geben? Evtl. fehlt eine Abhängigkeit? Wenn die Klasse nicht initialisiert werden konnte, dann bekommst Du nach außen auch eine ClassNotFoundException, aber die Klasse wurde gefunden nur eben konnte diese nicht geladen werden.

Bei Dir kann es also daran liegen, dass eine der static Elemente nicht gefunden wurde. Das würdest Du aber in den Child/Cause Exceptions sehen.
 
Ach so, vielleicht auch noch wichtig, zu erwähnen:

Ich habe in C:\Program Files\Microsoft\jdk-17.0.8.101-hotspot\conf\security\java.security Zeile 730 und 731 auskommentiert:

1694634522481.png

... und nutze das microsoft openjdk, damit der TLS Spaß funktioniert.^^
 
Wenn Du java -jar build/libs/MailNoti-1.0.jar startest, dann solltest Du zusammen mit der Exception auch den Stacktrace haben.

Evtl. ist das Problem, dass die Datei info-solid.png nicht im aktuellen Pfad ist? Aber das ist nur geraten. Da die Abhängigkeiten auf den ersten Blick korrekt in der jar Datei integriert wurden, wäre das ein erster Rateversuch, wenn ich mir die Initialisierung der Klasse ansehe.
 
Es kommt kein Stacktrace hoch. 😕

Das .png ist aber im aktuellen Verzeichnis.

Versuche mal bitte, wenn du Zeit hast, die Klasse und das .jar selber zu bauen, du müsstest nur ein Icon hinzufügen und user und pass anpassen. Dann könnte man zumindest ausschließen ob etwas mit meinem System/Java Installation nicht stimmt.
 
Es kommt kein Stacktrace hoch. 😕

Kann ich mir nicht vorstellen.

Wenn du Gradle ausführst, was kommt dann?

Wenn du die jar ausführst, was kommt dann?
 
Ja, das Problem mit dem StackTrace ist einfach, dass das Problem direkt beim laden der Hauptklasse entsteht. Da wird tatsächlich kein Stacktrace ausgegeben. Der Stacktrace kommt erst, wenn wirklich die main Methode aufgerufen wird.

Damit müssen wir wirklich über das Design sprechen, das so in der Form schlicht schlecht ist. Wieso dieser static initializer Block? Dafür gibt es hier einfach keinen Grund und es ist schlecht, sowas in der Hauptklasse zu machen. Mach daraus eine Methode init() und rufe diese in der main Methode als erstes auf und schon bekommst Du auch den Stacktrace zu sehen so es an einer Exception in dem static initializer Block liegt.

Generell solltest Du Dir aber überlegen, dass Du sowas objektorientiert aufbaust und Verantwortlichkeiten in diverse Klassen aufteilst. Aber das ist für die Kernproblematik des Threads unwichtig. Aber dadurch erreichst Du dann, dass es nur eine static Methode gibt (main), in der dann eine Instanz einer Klasse erstellt wird. Hier liegt es also nahe, dass Du die Funktionalitäten auftrennst: Verwaltung der Passwörter, Überwachen des Imap Kontos und die Verwaltung des SysTray Eintrags.
 
Damit müssen wir wirklich über das Design sprechen, das so in der Form schlicht schlecht ist. Wieso dieser static initializer Block? Dafür gibt es hier einfach keinen Grund und es ist schlecht, sowas in der Hauptklasse zu machen. Mach daraus eine Methode init() und rufe diese in der main Methode als erstes auf und schon bekommst Du auch den Stacktrace zu sehen so es an einer Exception in dem static initializer Block liegt.
Daran liegt es aber leider nicht, gleiches Verhalten.
 
Stell mal das aktuelle Projekt nach einem clean als zip bereit. Dann kann man mal genauer schauen, was da passiert.
 
Hallo zum Thema „static initializer Block“ mal eine Frage .
Nach meinem Verständnis ist es doch so das der Block noch vor dem Konstruktor durchlaufen wird. Nun ist es in dem Code vom TE so das er kein Objekt der Klasse Main erzeugt. Somit würde nach meiner Meinung nach der Block gar nicht durchlaufen werden.
Ist das Richtig so?


dann noch eine Frage wenn jetzt ein Objekt mit new erstellt wird. Was sind dann die Variablen in den static Block.
Sind es dann Klassenvariablen, Instanzvariablen, oder nur lokal in dem Block?
 
Hallo zum Thema „static initializer Block“ mal eine Frage .
Nach meinem Verständnis ist es doch so das der Block noch vor dem Konstruktor durchlaufen wird. Nun ist es in dem Code vom TE so das er kein Objekt der Klasse Main erzeugt. Somit würde nach meiner Meinung nach der Block gar nicht durchlaufen werden.
Ist das Richtig so?
Es ist ein static initializer block. Damit wird dieser direkt beim Laden der Klasse ausgeführt. Wenn die JVM gestartet wird, dann kommt der Punkt, dass die Main Klasse geladen werden soll. Dazu wird der Classloader angewiesen, die Klass ezu laden. Damit wird die .class Datei geladen und ausgewertet. An der Stelle läuft dann auch der static initializer block. Erst damit ist die Klasse fertig geladen.

Danach würde dann erst die main Methode aufgerufen.

Wenn nun beim Laden der Klasse eine Exception auftritt, dann wirft der ClassLoader eine ClassNotFoundException. Das tritt auch auf, wenn die Klasse selbst eigentlich gefunden wurde.

dann noch eine Frage wenn jetzt ein Objekt mit new erstellt wird. Was sind dann die Variablen in den static Block.
Sind es dann Klassenvariablen, Instanzvariablen, oder nur lokal in dem Block?
Die lokalen Variablen sind und bleiben lokale Variablen. Egal wo sich der Block befindet. Das ist aber unabhängig von der Erzeugung einer Instanz der Klasse. Das findet - wie Du schon selbst festgestellt hast - ja nicht statt in seinem Code.

Du musst halt sehen, dass der Classloader eine Instanz der Klasse Class erzeugt - mit den Daten aus der class Datei.
 
Es ist ein static initializer block. Damit wird dieser direkt beim Laden der Klasse ausgeführt. Wenn die JVM gestartet wird, dann kommt der Punkt, dass die Main Klasse geladen werden soll. Dazu wird der Classloader angewiesen, die Klass ezu laden. Damit wird die .class Datei geladen und ausgewertet. An der Stelle läuft dann auch der static initializer block. Erst damit ist die Klasse fertig geladen.

Danach würde dann erst die main Methode aufgerufen.

Wenn nun beim Laden der Klasse eine Exception auftritt, dann wirft der ClassLoader eine ClassNotFoundException. Das tritt auch auf, wenn die Klasse selbst eigentlich gefunden wurde.


Die lokalen Variablen sind und bleiben lokale Variablen. Egal wo sich der Block befindet. Das ist aber unabhängig von der Erzeugung einer Instanz der Klasse. Das findet - wie Du schon selbst festgestellt hast - ja nicht statt in seinem Code.

Du musst halt sehen, dass der Classloader eine Instanz der Klasse Class erzeugt - mit den Daten aus der class Datei.
danke
 
Es ist ein static initializer block. Damit wird dieser direkt beim Laden der Klasse ausgeführt. Wenn die JVM gestartet wird, dann kommt der Punkt, dass die Main Klasse geladen werden soll. Dazu wird der Classloader angewiesen, die Klass ezu laden. Damit wird die .class Datei geladen und ausgewertet. An der Stelle läuft dann auch der static initializer block. Erst damit ist die Klasse fertig geladen.
Nur noch kurz wenn ich jetzt einige Klassen mit static initializer block habe.
Dann würden die Blöcke vom Classloader schon aufgerufen werden bevor ich ein Objekt der Klassen erzeuge.
 
Stell mal das aktuelle Projekt nach einem clean als zip bereit. Dann kann man mal genauer schauen, was da passiert.
Juhu, ich konnte den Fehler finden 🙂

Und zwar lag es an den verwendeten Abhängigkeiten und an dem jar-Task in der build.gradle:

Code:
jar {
    manifest {
        attributes(
                'Main-Class': 'noti.Main'
        )
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    from {
        configurations.compileClasspath.filter{ it.exists() }.collect { it.isDirectory() ? it : zipTree(it) }
    }
    exclude 'META-INF/*.RSA'
    exclude 'META-INF/*.SF'
    exclude 'META-INF/*.DSA'
}

Es fehlte: exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'

Ein Stackoverflow-Thema hat mich darauf gebracht: https://stackoverflow.com/questions/51455197/gradle-fatjar-could-not-find-or-load-main-class ...

An dieser Stelle bitte einen Gradle-Rant loslasssen... 😉
 
Nur noch kurz wenn ich jetzt einige Klassen mit static initializer block habe.
Dann würden die Blöcke vom Classloader schon aufgerufen werden bevor ich ein Objekt der Klassen erzeuge.
Ja natürlich. Du kannst das ja auch einfach einmal Testen. So Code hatte ich schon einmal in einem anderen Thread geschrieben, wo es um die Reihenfolge der Ausführung ging. Also schauen wir einfach noch einmal.

Java:
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("Before class loading ...");
        Class testclass = Main.class.getClassLoader().loadClass("Testclass");
        System.out.println("After class loading ...");
        Testclass.printOnFieldInitialization("test");
        System.out.println("After first use!");
        Testclass instance = new Testclass();
        System.out.println("After creation of instance ...");
    }
}

Java:
public class Testclass {

    static {
        System.out.println("inside static block 1");
    }
    static int staticField1 = printOnFieldInitialization("static 1");

    static {
        System.out.println("inside static block 2");
    }

    static int staticField2 = printOnFieldInitialization("static 2");

    public static int printOnFieldInitialization(String text) {
        System.out.println("Field Initialization: " + text);
        return 1;
    }

    int field = printOnFieldInitialization("instance");

    public Testclass() {
        System.out.println("Constructor");
    }
}

Und dann erkennt man tatsächlich eine gewisse Ungenauigkeit in meinen bisherigen Aussagen. Denn die static Blöcke werden erst dann ausgeführt, wenn die Klasse tatsächlich das erste Mal benutzt wird. Noch nicht beim Laden der Klasse. Da findet als eine Art Lazy Loading statt.

Code:
Before class loading ...
After class loading ...
inside static block 1
Field Initialization: static 1
inside static block 2
Field Initialization: static 2
Field Initialization: test
Field Initialization: instance
After first use!
Constructor
After creation of instance ...
 
Stell mal das aktuelle Projekt nach einem clean als zip bereit. Dann kann man mal genauer schauen, was da passiert.
Wie versprochen, hier die zip.

Eine README liegt bei:

## Installation:

  • Import as Gradle project
  • Run: .\gradlew clean jar
  • Run: java -jar .\build\libs\MailNotiTest.jar
  • Optionally: Open ...\conf\security\java.security and comment out:

Code:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL

to make TLS work.
 

Anhänge


Zurück
Oben