javajarstructureexecutable-jarr.java-file

Jar File throws FileNotFoundException but eclipse does not


I use eclipse to do my java programming, last day i created a java program and i placed the /res folder to the root directory of the project, When i tested the program on eclipse it worked perfectly, but when i create an executable .jar file and i tried to run, I get the exception:

java.io.FileNotFoundException: res/music.wav (No such file or directory)
        at java.base/java.io.FileInputStream.open0(Native Method)
        at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
        at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
        at java.desktop/com.sun.media.sound.SunFileReader.getAudioInputStream(SunFileReader.java:117)
        at java.desktop/javax.sound.sampled.AudioSystem.getAudioInputStream(AudioSystem.java:1060)
        at main.MusicPlayer.run(MusicPlayer.java:21)
        at java.base/java.lang.Thread.run(Thread.java:1583)

It throws a FileNotFoundException but only when I run it as a .jar file. I saw that the jar file includes only the src/ folder so that's why it doesen't work. I tell someone and he told me that the res/ folder must be inside of ROOT directory. Can someone tell me how can i create a .jar file ? Where do i place the res/ folder ?

Also this is the code I use:

package main;

import java.io.File;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

public class MusicPlayer implements Runnable {

    private float volume = 0.3f;

    @Override
    public void run() {
        try {
//          InputStream is = getClass().getResourceAsStream("/res/music.wav");
//          BufferedInputStream bis = new BufferedInputStream(is);
            File bis = new File("res/music.wav");
            AudioInputStream ais = AudioSystem.getAudioInputStream(bis);
            AudioFormat format = ais.getFormat();
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);

            line.open(format);
            line.start();

            int nBytesRead = 0;
            byte[] abData = new byte[128000];
            while (nBytesRead != -1) {
                nBytesRead = ais.read(abData, 0, abData.length);
                if (nBytesRead >= 0) {
                    for (int i = 0; i < nBytesRead; i++) {
                        // Scale the sample by the volume factor
                        abData[i] = (byte) (abData[i] * volume);
                    }
                    line.write(abData, 0, nBytesRead);
                }
            }

            line.drain();
            line.close();
            ais.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And This is the Main.java file:

package main;

public class Main {
    public Main () {
        Thread musicThread = new Thread(new MusicPlayer());
        musicThread.start();
    }
    public static void main (String[] args) {
        new Main();
    }
}

Solution

  • There is nothing wrong with your program, you just need to confirm the directory structure.

    MusicPlayer.java

    Change back to your original InputStream writing method here.

        @Override
        public void run() {
            try {
                InputStream is = getClass().getResourceAsStream("/res/music.wav");
                BufferedInputStream bis = new BufferedInputStream(is);
                //File bis = new File("music.wav");
                AudioInputStream ais = AudioSystem.getAudioInputStream(bis);
    

    Project Tree

    It should be noted that an additional resources directory is created here, its type is Source Code. (New -> Source Folder)

    Eclipse Menu: File -> Export

    Step1:

    Step2:

    Jar File

    demo1.jar
    ├── main
    │   ├── Main.class
    │   └── MusicPlayer.class
    ├── META-INF
    │   └── MANIFEST.MF
    └── res
        └── music.wav
    

    Check MANIFEST.MF

    Manifest-Version: 1.0
    Main-Class: main.Main
    Class-Path: .
    

    Run

    java -jar demo1.jar
    

    Eclipse IDE Project Tree

    eclipse_WK/TMP1
    ├── bin
    │   ├── main
    │   │   ├── Main.class
    │   │   └── MusicPlayer.class
    │   └── res
    │       └── music.wav
    ├── resources
    │   └── res
    │       └── music.wav
    └── src
        └── main
            ├── Main.java
            └── MusicPlayer.java
    

    When Eclipse executes your program code, it will compile it first, and then put the result in the bin directory. It will compile

    <?xml version="1.0" encoding="UTF-8"?>
    <classpath>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
            <attributes>
                <attribute name="module" value="true"/>
            </attributes>
        </classpathentry>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="resources"/>
        <classpathentry kind="output" path="bin"/>
    </classpath>
    

    As you can see, there are two Source Folders:

    There is also an Output Folder:

    ├── bin
    │   ├── main
    │   │   ├── Main.class
    │   │   └── MusicPlayer.class
    │   └── res
    │       └── music.wav
    

    Here you can see the structure of the bin directory. In fact, it is just like preparing your own directory on the command line. You can execute it without packaging it into a jar.

    You can do this:

    cd bin
    java -cp . main.Main
    

    Note 1: that here is -cp . which is equivalent to the Class-Path: . of MANIFEST.MF in the Jar file.

    Note 2: When Eclipse executes your Java, it is equivalent to manually executing the contents of the bin directory in Terminal. It will not be packaged into a jar file first.

    Note 2 is the answer to the question in your comments.

    Your comments:

    so i think eclipse ide extracts only resources placed inside of resource directories in the .jar file...
    

    You can find that the directory structure in the bin directory and jar is the same.

    demo1.jar
    ├── main
    │   ├── Main.class
    │   └── MusicPlayer.class
    ├── META-INF
    │   └── MANIFEST.MF
    └── res
        └── music.wav
    

    META-INF/MANIFEST.MF is automatically generated by Eclipse when you export as a Runnable JAR file.

    The newer approach is to use Maven or Gradle projects.

    Maven Project Tree

    TMP2
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           ├── Main.java
            │           └── MusicPlayer.java
            └── resources
                └── res
                    └── music.wav
    

    The directory name main will be used in Maven or Gradle. To avoid misunderstanding, do not use main as the package name.

    Main.java and MusicPlayer.java need to be modified: package com.example;

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.example</groupId>
        <artifactId>music-player</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>music-player</name>
        <description>Music Player Project</description>
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
        <build>
            <finalName>app</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.3.0</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>com.example.Main</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    Build And Run

    Build

    mvn clean package
    

    Run

    java -jar target/app.jar
    

    Eclipse supports Maven projects.

    Eclipse Menu: File -> Import -> Maven -> Existing Maven Projects