Search code examples
javajar

How can i make a simple self extracting jar file?


basically i need a jar installer, that extracts cert.pem and cloudflare.exe from itself into a temporary directory, and withnout using any 3rd party libraries.

I tried to find some similar questions, but i only found outdated questions, or questions that werent useful.

Contents of the jar file for reference:


Solution

  • A self-extracting jar (still needing a java installation)

    package com.stackoverflow.joopeggen;
    
    import java.io.IOException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.nio.file.*;
    import java.security.ProtectionDomain;
    import java.util.Map;
    
    public class App {
    
        public static void main(String[] args) {
            try {
                new App().extractSelf();
            } catch (IOException | URISyntaxException| IllegalStateException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    

    The extraction uses class information to get the jar source URL file: ... .jar.

        private void extractSelf() throws IOException, URISyntaxException {
            ProtectionDomain protectionDomain = App.class.getProtectionDomain();
            URL fileUrl = protectionDomain.getCodeSource().getLocation();
            // "file: ... .jar"
            Path jarDir = Paths.get(fileUrl.toURI()).getParent();
            URI jarFileUri = URI.create("jar:" + fileUrl);
            Map<String, Object> env = Map.of("Encode", "UTF-8");
            FileSystem zipFS = FileSystems.newFileSystem(jarFileUri, env);
            Path root = zipFS.getPath("/");
            Files.list(root)
                    .filter(path -> Files.isRegularFile(path))
                    .forEach(path -> {
                        try {
                            Path extractedPath = jarDir.resolve(path.getFileName().toString());
                            System.out.println("* " + extractedPath);
                            Files.copy(path, extractedPath);
                        } catch (IOException e) {
                            throw new IllegalStateException(path.getFileName().toString(), e);
                        }
                    });
        }
    }
    

    The main class needs to be inside the META-INF/MANIFEST.MF.

    I have used maven for the build.

    • src/main/java - the root for java sources
      • com/stackoverflow/joopeggen - the package
    • src/main/resources - the root for resource files
      • cert.pem
      • cloudflare.exe

    As you might not be familiar with maven, the Project-Object-Model XML, 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.stackoverflow.joopeggen</groupId>
    <artifactId>selfextracting</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <!-- exec:exec to start the jar instead of generated classes. -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>maven</executable>
                </configuration>
            </plugin>
            <plugin>
                <!-- To fill META-INF/MANIFEST.MF with Main-Class. -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>com.stackoverflow.joopeggen.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    </project>
    

    The code only requires a few lines (approx. 17), though many aspects are touched.