1

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.

PC.
  • 6,870
  • 5
  • 36
  • 71

1 Answers1

2

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.

niksu
  • 61
  • 4
  • Yes.. I can see the comment - `"To load classes in JAR files within a JAR file into the class path, you must write custom code to load those classes"`. Any idea what code should be used to load them? – PC. Dec 28 '17 at 13:21
  • As I said, it's not a good solution to write a custom class loader for this. Without any code changes you could just keep the external jar files in "../lib" folder (or rather edit the ant script so that the Class-Path entry points to ./lib instead and keep the jars there). The jars are found in the class path (specified by Class-Path in the manifest), but not from inside your jar. – niksu Dec 29 '17 at 14:01
  • See also old post on the issue: https://stackoverflow.com/questions/183292/classpath-including-jar-within-a-jar – niksu Dec 29 '17 at 14:03