NoClassDefFoundError after creating executable jar with libraries using ant

872 Views Asked by At

I have created a sample application to demonstrate the problem I'm facing. My sample app contains single class with main method and a dependency on log4j

package com.example.foo;

public class MainClass {
    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(MainClass.class);

    public static void main(String[] args) {
        LOG.info("Hello Ant Build");
    }
}

I have added two jar files slf4j-api-1.7.25.jar and slf4j-simple-1.7.25.jar in my lib folder.Project Stricture

Next, I have created an ant script to build this project. (This project runs successfully under eclipse, but that is not what I need)

My build.xml is

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project name="sample-app" basedir="../SampleApp" default="jar">
    <property name="source.dir" value="src"/>
    <property name="lib.dir" value="lib"/>
    <property name="class.dir" value="bin"/>
    <property name="jar.dir" value="dist"/>
    <property name="jar.file" value="${jar.dir}/${ant.project.name}.jar"/>
    <property name="main-class" value="com.example.foo.MainClass"/>

    <path id="libraries.path">    
        <fileset dir="${lib.dir}">
            <include name="*.jar"/>
        </fileset>
    </path>

    <target name="clean" description="delete old files">
        <delete dir="${class.dir}"/>
        <delete dir="${jar.dir}"/>
    </target>

    <target name="compile" description="build class files" depends="clean">
        <mkdir dir="${class.dir}"/>
        <javac srcdir="${source.dir}" destdir="${class.dir}" includeantruntime="false">
            <classpath refid="libraries.path"/>
        </javac>
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <mkdir dir="${class.dir}/${lib.dir}"/>
        <copy todir="${class.dir}/${lib.dir}" flatten="true">
            <path refid="libraries.path"/>
        </copy>

        <manifestclasspath property="manifest.classpath" jarfile="${jar.file}">
            <classpath refid="libraries.path"/>
        </manifestclasspath>

        <jar destfile="${jar.file}" basedir="${class.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
                <attribute name="Class-Path" value="${manifest.classpath}"/>
            </manifest>
        </jar>  
    </target>
</project>

This creates the jar file successfully. All the required class files and jar files are present in the jar file.

Jar Contents

But when I execute the jar using java -jar sample-app.jar I get NoClassDefFoundError.

$ java -jar sample-app.jar
java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
        at com.example.foo.MainClass.<clinit>(Unknown Source)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more
Exception in thread "main"

The contents of MANIFEST.MF file is:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.10.1
Created-By: 1.8.0_111-b14 (Oracle Corporation)
Main-Class: com.example.foo.MainClass
Class-Path: ../lib/slf4j-api-1.7.25.jar ../lib/slf4j-simple-1.7.25.jar

I'm not able to figure out what is wrong or missing in the jar file. Please help me figure out the issue with my ant script. Thanks in advance.

1

There are 1 best solutions below

3
niksu On

Standard class loader will not load jar files from inside your jar (see https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html).

With the given Class-Path entry in MANIFEST.MF, those jar files need to be placed to folder "../lib", meaning "lib" folder in the parent folder of the directory where the executable jar is executed.

Other solutions are:

  1. Write custom class loader
  2. Extract the slf4j jars and repackage the contents

I wouldn't recommend either of these.