I've developed a JavaFX Todo application that reads and writes tasks to a CSV file. The application works perfectly in my development environment (IntelliJ). However, I'm running into issues when exporting the application as a JAR file and eventually converting it into an EXE file for distribution.
When I run the JAR file, the application is unable to locate the CSV file. I'm unsure how to package the CSV file correctly with the JAR, or how to ensure that the application can read and write to the CSV file once it's exported.
How should I structure my project and configure the FileService
class so that the CSV file is accessible when the application is exported as a JAR?
Is there a recommended way to handle file paths for resources like CSV files when distributing a JavaFX application as a JAR or EXE?
The CSV file is currently located in the Resources/Database/ directory within my project structure. I'm planning to convert the JAR to an EXE for easier distribution to friends.
Any advice on the correct approach or best practices would be greatly appreciated!
.
└── src/
├── Controller/
│ ├── TodoController
│ └── TodoItemController
├── Model/
│ └── Status
├── View/
│ ├── TodoItem.fxml
│ └── TodoManager.fxml
├── Util/
│ └── AlerUtil
├── Service/
│ └── TodoManager
└── Resources/
├── Database/
│ └── tasks.csv
├── bin.png
├── plus.png
└── logo.png
package Service;
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.exceptions.CsvException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class FileService {
private final String filePath = "src/Resources/Database/tasks.csv";
/**
* @return List of String Array representing rows and columns
*/
public List<String[]> readCsv() {
List<String[]> data = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader(filePath))) {
// skip headers row 1.
reader.skip(1);
// read the rest of the rows
data = reader.readAll();
} catch (FileNotFoundException e) {
System.err.println("Unable to locate task.csv " + e);
} catch (IOException | CsvException e) {
System.err.println(e.getMessage());
}
return data;
}
public void appendData(String[] record) {
// passing true as the second parameter for append
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath, true))) {
// appending the record
writer.writeNext(record);
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
public void writeData(List<String[]> data) {
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) {
// write the header
writer.writeNext(new String[] {"task", "dateCreated", "status", "dateCompleted"});
// write the data
writer.writeAll(data);
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
I created a JAR file using IntelliJ.
I attempted to make the CSV file accessible by placing it in a folder on my desktop and updating the FileService class to point to this file path.
I exported the application to this folder, but the application could not access the CSV file when run from the JAR.
How should I structure my project
You should follow the maven standard directory layout using a build tool (I recommend Maven, but you can use Gradle if you prefer). While you could use a non-standard layout, there is seldom a good reason to do so except if integrating a very large legacy project under limited time constraints.
Put your code under:
src/main/java
and your read-only resources under:
src/main/resources
The Idea new JavaFX project wizard will set up a working project with this structure for you, configuring a build tool of your choice. The wizard will provide a sample FXML file and usage, but you can remove that if you don't use FXML.
Loading follows the pattern from:
Which is generally going to be:
URL resourceUrl = SomeClass.class.getResource(location);
OR
InputStream resourceStream = SomeClass.class.getResourceAsStream(location);
Where the location is a String with these properties:
/
for an absolute location OR prefixed with nothing for a relative location to SomeClass
..
parent path navigation is not used.src
is never included in the location string.A complete guide is at:
However, you want to be able to also write to your data file. So a read-only resource is either not useful or only useful for providing initial default data to be copied elsewhere (e.g. to a file on the file system or in a database store separate from the app).
The Makery JavaFX tutorial provides info on a read/write data storage mechanism. It uses an XML storage format, but CSV would also be OK.
Examples for copying default read-only resource data to a writable location (if you need to do that):
Distribution is a tricky topic but a jlink zip is easy to create. jpackage
is more difficult, but can create an installer if you need one. More info can be found in the packaging section under in the JavaFX tag.