63

I want to copy a file from a jar. The file that I am copying is going to be copied outside the working directory. I have done some tests and all methods I try end up with 0 byte files.

EDIT: I want the copying of the file to be done via a program, not manually.

Anuj Balan
  • 7,629
  • 23
  • 58
  • 92
jtl999
  • 1,115
  • 2
  • 10
  • 17
  • Do you have to do this programatically? If you don't, you can rename your `Foo.jar` to `Foo.zip` and unzip it normally. – Jeffrey Apr 25 '12 at 01:32
  • Show us the code (the tests) that doesn't work. 1) We may be able to explain why it doesn't work, 2) The code may help us understand what it is you are *actually trying to do* here. – Stephen C Apr 25 '12 at 02:41
  • Voting to close. This question is STILL too vague / poorly written to be answered without a lot of guesswork, and therefore unlikely to be useful to other readers. – Stephen C Apr 25 '12 at 05:28
  • Also, a duplicate of this: http://stackoverflow.com/questions/2271926/how-to-read-a-file-from-a-jar-file – Stephen C Apr 25 '12 at 05:32
  • 3
    Not a duplicate of that question, which covers reading, not copying. – Andy Thomas Oct 23 '17 at 16:08

9 Answers9

63

First of all I want to say that some answers posted before are entirely correct, but I want to give mine, since sometimes we can't use open source libraries under the GPL, or because we are too lazy to download the jar XD or what ever your reason is here is a standalone solution.

The function below copy the resource beside the Jar file:

  /**
     * Export a resource embedded into a Jar file to the local file path.
     *
     * @param resourceName ie.: "/SmartLibrary.dll"
     * @return The path to the exported resource
     * @throws Exception
     */
    static public String ExportResource(String resourceName) throws Exception {
        InputStream stream = null;
        OutputStream resStreamOut = null;
        String jarFolder;
        try {
            stream = ExecutingClass.class.getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
            if(stream == null) {
                throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
            }

            int readBytes;
            byte[] buffer = new byte[4096];
            jarFolder = new File(ExecutingClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getPath().replace('\\', '/');
            resStreamOut = new FileOutputStream(jarFolder + resourceName);
            while ((readBytes = stream.read(buffer)) > 0) {
                resStreamOut.write(buffer, 0, readBytes);
            }
        } catch (Exception ex) {
            throw ex;
        } finally {
            stream.close();
            resStreamOut.close();
        }

        return jarFolder + resourceName;
    }

Just change ExecutingClass to the name of your class, and call it like this:

String fullPath = ExportResource("/myresource.ext");

Edit for Java 7+ (for your convenience)

As answered by GOXR3PLUS and noted by Andy Thomas you can achieve this with:

Files.copy( InputStream in, Path target, CopyOption... options)

See GOXR3PLUS answer for more details

Ordiel
  • 2,442
  • 3
  • 36
  • 52
  • 1
    The funny thing is that I had to do this exact task for another program I was working on. This works. Thanks! – jtl999 Jul 17 '13 at 17:17
  • 1
    Thanks Ordiel, but why we set the buffer szie as 4 bytes? – hackjutsu Aug 19 '16 at 18:10
  • 1
    4 bytes? (i think you mean 4 Kilobytes) easy, it is a very standard sector size in most hard drives, your hardware reads by sectors so "theoretically" (many other things are involved and we can talk about this for hours) this helps to achieve a high level of efficiency, if you make it lower you will be asking your hard drive to read twice the same sector to get data you could have get with one single physical read operation on your HD – Ordiel Aug 21 '16 at 07:09
  • 2
    In Java 7+, some of this code can be replaced with a simple call to `Files.copy( InputStream in, Path target, CopyOption... options)`, as noted by a more recent answer below. – Andy Thomas Dec 04 '17 at 23:01
  • 1
    You can get NPEs in your 'finally' clause because you're not checking to see if the stream or resStreamOut are null. – Evvo Oct 24 '19 at 17:48
42

Given your comment about 0-byte files, I have to assume you're trying to do this programmatically, and, given your tags, that you're doing it in Java. If that's true, then just use Class.getResource() to get a URL pointing to the file in your JAR, then Apache Commons IO FileUtils.copyURLToFile() to copy it out to the file system. E.g.:

URL inputUrl = getClass().getResource("/absolute/path/of/source/in/jar/file");
File dest = new File("/path/to/destination/file");
FileUtils.copyURLToFile(inputUrl, dest);

Most likely, the problem with whatever code you have now is that you're (correctly) using a buffered output stream to write to the file but (incorrectly) failing to close it.

Oh, and you should edit your question to clarify exactly how you want to do this (programmatically, not, language, ...)

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • This answer *assumes* that the JAR file is on the classpath. This may not be the case. And even if it is the case, there may be another JAR file on the classpath that hides the specific file the OP is trying to extract. – Stephen C Apr 25 '12 at 02:38
  • True enough, but if my assumptions are correct, this seems to me the most likely scenario. If I'm wrong, there are other answers here that will take care of it. – Ryan Stewart Apr 25 '12 at 03:39
  • Good interpretation of the question before the edit was given. – Anuj Balan Apr 25 '12 at 05:28
  • For people looking for commons-io dependency: https://mvnrepository.com/artifact/commons-io/commons-io – prayagupa Jan 25 '22 at 00:19
18

Faster way to do it with Java 7+ , plus code to get the current directory:

   /**
     * Copy a file from source to destination.
     *
     * @param source
     *        the source
     * @param destination
     *        the destination
     * @return True if succeeded , False if not
     */
    public static boolean copy(InputStream source , String destination) {
        boolean succeess = true;

        System.out.println("Copying ->" + source + "\n\tto ->" + destination);

        try {
            Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            logger.log(Level.WARNING, "", ex);
            succeess = false;
        }

        return succeess;

    }

Testing it (icon.png is an image inside the package image of the application):

copy(getClass().getResourceAsStream("/image/icon.png"),getBasePathForClass(Main.class)+"icon.png");

About the line of code (getBasePathForClass(Main.class)): -> check the answer i have added here :) -> Getting the Current Working Directory in Java

Community
  • 1
  • 1
GOXR3PLUS
  • 6,877
  • 9
  • 44
  • 93
14

Java 8 (actually FileSystem is there since 1.7) comes with some cool new classes/methods to deal with this. As somebody already mentioned that JAR is basically ZIP file, you could use

final URI jarFileUril = URI.create("jar:file:" + file.toURI().getPath());
final FileSystem fs = FileSystems.newFileSystem(jarFileUri, env);

(See Zip File)

Then you can use one of the convenient methods like:

fs.getPath("filename");

Then you can use Files class

try (final Stream<Path> sources = Files.walk(from)) {
     sources.forEach(src -> {
         final Path dest = to.resolve(from.relativize(src).toString());
         try {
            if (Files.isDirectory(from)) {
               if (Files.notExists(to)) {
                   log.trace("Creating directory {}", to);
                   Files.createDirectories(to);
               }
            } else {
                log.trace("Extracting file {} to {}", from, to);
                Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
            }
       } catch (IOException e) {
           throw new RuntimeException("Failed to unzip file.", e);
       }
     });
}

Note: I tried that to unpack JAR files for testing

wrestang
  • 553
  • 7
  • 17
Lukino
  • 1,407
  • 13
  • 14
  • Very interesting, will look into this for future projects :) – jtl999 Apr 15 '15 at 23:42
  • I added it here, just for everybody else who may run into this and why not use new/shorter approach. – Lukino Apr 16 '15 at 14:55
  • `"jar:file:"+file.toURI().getPath()` looks somewhat nonsensical. Why not simply `"jar:"+file.toURI()`…  by the way, it’s `toURI()` rather than `toUri()` – Holger Jul 06 '16 at 13:38
  • @Holger yes this is important as URI will be converted to String using toString() which includes 'schema" (see documentation). If you look at results, URI.toString() have 'file:///foo/bar...' where getPath() has have only '/foo/bar'. Fair point on URI.... toUri is part of Path class. now I am not sure if I my variable 'file' was File or Path. If it was File, then it is toURI, but if Path then I don't need to use toUri as I can use it directly. – Lukino Jul 06 '16 at 14:34
  • Maybe you should look more carefully. You make the call to `getPath()` to remove the schema, then prepend a string containing the very same schema. I posted an alternative with *both* removed, hence, the result will be `jar:file:/foo/bar`, exactly as with your variant. You may try it if you don’t believe. – Holger Jul 06 '16 at 14:49
8

Robust solution:

public static void copyResource(String res, String dest, Class c) throws IOException {
    InputStream src = c.getResourceAsStream(res);
    Files.copy(src, Paths.get(dest), StandardCopyOption.REPLACE_EXISTING);
}

You can use it like this:

File tempFileGdalZip = File.createTempFile("temp_gdal", ".zip");
copyResource("/gdal.zip", tempFileGdalZip.getAbsolutePath(), this.getClass());
zoran
  • 943
  • 11
  • 22
2

Use the JarInputStream class:

// assuming you already have an InputStream to the jar file..
JarInputStream jis = new JarInputStream( is );
// get the first entry
JarEntry entry = jis.getNextEntry();
// we will loop through all the entries in the jar file
while ( entry != null ) {
  // test the entry.getName() against whatever you are looking for, etc
  if ( matches ) {
    // read from the JarInputStream until the read method returns -1
    // ...
    // do what ever you want with the read output
    // ...
    // if you only care about one file, break here 
  }
  // get the next entry
  entry = jis.getNextEntry();
}
jis.close();

See also: JarEntry

jarrad
  • 3,292
  • 1
  • 23
  • 15
0

To copy a file from your jar, to the outside, you need to use the following approach:

  1. Get a InputStream to a the file inside your jar file using getResourceAsStream()
  2. We open our target file using a FileOutputStream
  3. We copy bytes from the input to the output stream
  4. We close our streams to prevent resource leaks

Example code that also contains a variable to not replace the existing values:

public File saveResource(String name) throws IOException {
    return saveResource(name, true);
}

public File saveResource(String name, boolean replace) throws IOException {
    return saveResource(new File("."), name, replace)
}

public File saveResource(File outputDirectory, String name) throws IOException {
    return saveResource(outputDirectory, name, true);
}

public File saveResource(File outputDirectory, String name, boolean replace)
       throws IOException {
    File out = new File(outputDirectory, name);
    if (!replace && out.exists()) 
        return out;
    // Step 1:
    InputStream resource = this.getClass().getResourceAsStream(name);
    if (resource == null)
       throw new FileNotFoundException(name + " (resource not found)");
    // Step 2 and automatic step 4
    try(InputStream in = resource;
        OutputStream writer = new BufferedOutputStream(
            new FileOutputStream(out))) {
         // Step 3
         byte[] buffer = new byte[1024 * 4];
         int length;
         while((length = in.read(buffer)) >= 0) {
             writer.write(buffer, 0, length);
         }
     }
     return out;
}
Ferrybig
  • 18,194
  • 6
  • 57
  • 79
-2

A jar is just a zip file. Unzip it (using whatever method you're comfortable with) and copy the file normally.

Chris Eberle
  • 47,994
  • 12
  • 82
  • 119
-3
${JAVA_HOME}/bin/jar -cvf /path/to.jar
Alexander Pogrebnyak
  • 44,836
  • 10
  • 105
  • 121