I'm making an auto-clicker app that runs in the background with jnativehook in the tray, which can open up a java.awt app when the icon is clicked. That whole part works when run from vscode with run java. When I packaged the project with mvn clean package to get the shaded executable JAR, the icon image for the app looks weird. (it was working fine when run from vscode). I also tried the exe application in the target folder, but I got the same results.
I saw that the icon in the pom.xml had to be an ICO file instead of a PNG, so I converted the 16x16 image with https://convertico.com/ and it still doesn't work.
I also tried just taking out the icon in the pom.xml since it was already defined in the tray part of the Java files, but I saw no change.
When run from my main file in vscode from run java picture
My Tray.java file where I handle the tray and set the icon image for the app that works perfectly when run from the main file:
package com.autoclicker;
import java.awt.*;
import java.awt.event.*;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
public class Tray {
private TrayIcon trayIcon;
private App app;
private SettingsManager settingsManager;
public Tray() {
setupTray();
app = new App();
settingsManager = new SettingsManager();
// Initialize global key listener using current settings
new KeyListener();
}
/**
* Sets up the system tray icon and its associated context menu.
* If system tray is not supported, this method does nothing.
* The system tray icon is set to the image located at
* src/main/res/img/icon.png.
* The context menu has one item: "Exit", which exits the application
* and unregisters the native hook.
*/
private void setupTray() {
if (!SystemTray.isSupported()) {
System.err.println("System tray is not supported.");
return;
}
SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().getImage("src\\main\\res\\img\\icon.png");
trayIcon = new TrayIcon(image, "Auto Clicker");
trayIcon.setImageAutoSize(true);
trayIcon.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// Left-click to open the settings window
if (e.getButton() == MouseEvent.BUTTON1) {
app.showSettingsWindow();
}
}
});
PopupMenu popup = new PopupMenu();
MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(e -> {
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException ex) {
ex.printStackTrace();
}
System.exit(0);
});
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println("TrayIcon could not be added.");
e.printStackTrace();
}
}
}
My pom.xml, which is where I set the icon for the project (I don't know very much about pom.xml):
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.autoclicker</groupId>
<artifactId>auto-clicker</artifactId>
<version>1.0-SNAPSHOT</version>
<name>auto-clicker</name>
<!-- FIXME: change to your project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- Test Dependency -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- JNativeHook (global mouse/keyboard listeners) -->
<dependency>
<groupId>com.1stleg</groupId>
<artifactId>jnativehook</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Gson for JSON parsing -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<!-- Lock down plugin versions for standard lifecycle plugins -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!-- Maven Shade Plugin: creates an uber-jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- Attach the shaded JAR with classifier "shaded" -->
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
<transformers>
<!-- Add the manifest specifying your main class (change this if necessary) -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.autoclicker.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- Launch4j Maven Plugin: wraps the shaded JAR into a Windows EXE -->
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>2.5.3</version>
<executions>
<execution>
<id>l4j-wrapper</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
</execution>
</executions>
<configuration>
<jar>${project.build.directory}/${project.build.finalName}-shaded.jar</jar>
<outfile>${project.build.directory}/${project.build.finalName}.exe</outfile>
<headerType>gui</headerType>
<chdir>.</chdir>
<icon>src/main/res/img/icon.ico</icon>
<jre>
<minVersion>1.8.0</minVersion>
</jre>
</configuration>
</plugin>
</plugins>
</build>
</project>
The main file is simply running the Tray.java file which runs the App.java file when clicked on in the system tray:
package com.autoclicker;
import javax.swing.SwingUtilities;
public class Main {
/**
* Main entry point of the application.
*
* This method creates a new instance of the Tray class, which sets up the
* system tray and the settings window.
*
* @param args Command line arguments (not used)
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Tray());
}
}
Here's the repo: https://github.com/JeromeyDynamics/Auto-Clicker-App
Minimal reproducible example
import java.awt.*;
import java.awt.event.*;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
public class Tray {
public static void main(String[] args) {
SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().getImage("src\\main\\res\\img\\icon.png");
TrayIcon trayIcon = new TrayIcon(image, "Auto Clicker");
trayIcon.setImageAutoSize(true);
PopupMenu popup = new PopupMenu();
MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(e -> {
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException ex) {
ex.printStackTrace();
}
System.exit(0);
});
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println("TrayIcon could not be added.");
e.printStackTrace();
}
}
}
1 Answer 1
https://github.com/JeromeyDynamics/Auto-Clicker-App
Project Diectory
Your version
Auto-Clicker-App-main
├── pom.xml
└── src
├── main
│ ├── java
...
│ └── res
│ └── img
│ ├── icon.ico
│ └── icon.png
Change your icon directory to follow Maven conventions.
Auto-Clicker-App-main
├── pom.xml
└── src
├── main
│ ├── java
...
│ └── resources
│ ├── icon.ico
│ └── icon.png
...
pom.xml
I also made some adjustments to the pom.xml based on my usual Maven configuration practice: if the default settings work, there's no need to override them. The goal is to keep the pom.xml as minimal and clean as possible.
<?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.autoclicker</groupId>
<artifactId>auto-clicker</artifactId>
<version>1.0-SNAPSHOT</version>
<name>auto-clicker</name>
<description>Demo App Project</description>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- Test Dependency -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- JNativeHook (global mouse/keyboard listeners) -->
<dependency>
<groupId>com.1stleg</groupId>
<artifactId>jnativehook</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Gson for JSON parsing -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Shade Plugin: creates an uber-jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- Attach the shaded JAR with classifier "shaded" -->
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
<transformers>
<!-- Add the manifest specifying your main class (change this if necessary) -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.autoclicker.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- Launch4j Maven Plugin: wraps the shaded JAR into a Windows EXE -->
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>2.5.3</version>
<executions>
<execution>
<id>l4j-wrapper</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
</execution>
</executions>
<configuration>
<jar>${project.build.directory}/${project.build.finalName}-shaded.jar</jar>
<outfile>${project.build.directory}/${project.build.finalName}.exe</outfile>
<!--
<headerType>console</headerType>
-->
<headerType>gui</headerType>
<chdir>.</chdir>
<icon>src/main/resources/icon.ico</icon>
<jre>
<minVersion>1.8.0</minVersion>
</jre>
</configuration>
</plugin>
</plugins>
</build>
</project>
Also, when debugging, please change the headerType of the launch4j-maven-plugin to console by setting <headerType>console</headerType>.
When headerType is set to gui, any runtime errors may not be shown because the application runs as a GUI, which hides console output.
Therefore, during development and debugging, it's recommended to set headerType to console.
Main.java
Add a shared method named loadIcon in Main.java to load the icon.
package com.autoclicker;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Main {
public static Image loadIcon(String path) {
URL imageUrl = Main.class.getResource(path);
if (imageUrl != null) {
return Toolkit.getDefaultToolkit().getImage(imageUrl);
} else {
System.err.println("Icon not found:" + path);
return null;
}
}
/**
* Main entry point of the application.
* <p>
* This method creates a new instance of the Tray class, which sets up the
* system tray and the settings window.
*
* @param args Command line arguments (not used)
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Tray());
}
}
App.java
There are two changes made here:
The icon is now loaded by calling
Main.loadIcon()instead of loading it directly.The frame icon is explicitly set to your
icon.png. (If not set, it defaults to the standard Java coffee cup icon.)
Additionally, the original code attempted to load the icon using the incorrect file name pixil-frame-0.png, whereas the actual file is named icon.png. This mismatch was the reason the file couldn't be found.
App.java
// Image icon = Toolkit.getDefaultToolkit().getImage("auto-clicker\\src\\main\\res\\img\\pixil-frame-0.png");
Image icon = Main.loadIcon("/icon.png");
if (icon != null) {
settingsFrame.setIconImage(icon); // ✅ SET ICON
}
Tray.java
Tray.java
// Image image = Toolkit.getDefaultToolkit().getImage("src\\main\\res\\img\\icon.png");
Image image = Main.loadIcon("/icon.png");
Build
mvn clean package
Run exe
You can see that the tray icon in the bottom-right corner is now displayed correctly. The frame icon in the top-left corner has been updated to icon.png, and the icon for the label has also been changed.
1 Comment
Explore related questions
See similar questions with these tags.
icon.png, the path to that file is notsrc\main\res\img\icon.pngsrc\\main\\res\\img\\icon.png. can you try adding a relative path or creating the folder structure inside the target folder where you are running the .exeImageIO