Java: Programmatically Update Jar File

Posted on

Java programs are all packaged into Jar files. So when packaging large Java applications we sometimes need to add extra resources and usually we package those into separate jar files as well. Then there comes a time when we might need to update these jar files. Either to remove, add or edit something.

Jar files are really annoying because you cannot just open one and update it. You have to create a copy, update it and then delete the old one. I have created a nice convenience method to do this with the single stroke of the keyboard. It handles errors reasonably that if anything happens the jar file in question does not get affected.

	public void updateJarFile(File srcJarFile, String targetPackage, File ...filesToAdd) throws IOException {
		File tmpJarFile = File.createTempFile("tempJar", ".tmp");
		JarFile jarFile = new JarFile(srcJarFile);
		boolean jarUpdated = false;

		try {
			JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(tmpJarFile));

			try {
				//Added the new files to the jar.
				for(int i=0; i < filesToAdd.length; i++) {
					File file = filesToAdd[i];
					FileInputStream fis = new FileInputStream(file);
					try {
						byte[] buffer = new byte[1024];
						int bytesRead = 0;
						JarEntry entry = new JarEntry(targetPackage+File.separator+file.getName());
						tempJarOutputStream.putNextEntry(entry);
						while((bytesRead = fis.read(buffer)) != -1) {
							tempJarOutputStream.write(buffer, 0, bytesRead);
						}

						//System.out.println(entry.getName() + " added.");
					}
					finally {
						fis.close();
					}
				}

				//Copy original jar file to the temporary one.
				Enumeration jarEntries = jarFile.entries();
				while(jarEntries.hasMoreElements()) {
					JarEntry entry = jarEntries.nextElement();
					InputStream entryInputStream = jarFile.getInputStream(entry);
					tempJarOutputStream.putNextEntry(entry);
					byte[] buffer = new byte[1024];
					int bytesRead = 0;
					while ((bytesRead = entryInputStream.read(buffer)) != -1) {
						tempJarOutputStream.write(buffer, 0, bytesRead);
					}
				}

				jarUpdated = true;
			}
			catch(Exception ex) {
				ex.printStackTrace();
				tempJarOutputStream.putNextEntry(new JarEntry("stub"));
			}
			finally {
				tempJarOutputStream.close();
			}

		}
		finally {
			jarFile.close();
			//System.out.println(srcJarFile.getAbsolutePath() + " closed.");

			if (!jarUpdated) {
				tmpJarFile.delete();
			}
		}

		if (jarUpdated) {
			srcJarFile.delete();
			tmpJarFile.renameTo(srcJarFile);
			//System.out.println(srcJarFile.getAbsolutePath() + " updated.");
		}
	}
Advertisements

5 thoughts on “Java: Programmatically Update Jar File

    SUJEESH M A said:
    July 30, 2015 at 8:01 am

    Using the above function, i am able to add new class file but if a class file is already exist then it is not getting modified and throws an error. Please help.

    java.util.zip.ZipException: duplicate entry: com/test/abcd.class
    at java.util.zip.ZipOutputStream.putNextEntry(ZipOutputStream.java:215)
    at java.util.jar.JarOutputStream.putNextEntry(JarOutputStream.java:109)

    geodma responded:
    September 17, 2015 at 12:31 pm

    Ah yes, you would probably have to remove it and then add the replacement. I think this is how zip/jar files work. I’ve only used the code to add new files, I’ve never tried replacing existing ones.

    Good to know it will get this error though, thanks.

    Mark Leone said:
    March 3, 2016 at 10:01 pm

    Thanks for this. I needed a couple extras features, so I modified it a bit. I added an optional capability to update existing files in the archive, per SUJEESH’s observation, and an ability to get an InputStream for a single Jar entry.

    public class JarUtil {

    public static void updateJarFile(File srcJarFile, String targetPackage, boolean update, File …filesToAdd) throws IOException {
    File tmpJarFile = File.createTempFile(“tempJar”, “.tmp”);
    JarFile jarFile = new JarFile(srcJarFile);
    boolean jarUpdated = false;
    List fileNames = new ArrayList();

    try {
    JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(tmpJarFile));

    try {
    //Added the new files to the jar.
    for(int i=0; i < filesToAdd.length; i++) {
    File file = filesToAdd[i];
    FileInputStream fis = new FileInputStream(file);
    try {
    byte[] buffer = new byte[1024];
    int bytesRead = 0;
    JarEntry entry = new JarEntry(targetPackage+File.separator+file.getName());
    fileNames.add(entry.getName());
    tempJarOutputStream.putNextEntry(entry);
    while((bytesRead = fis.read(buffer)) != -1) {
    tempJarOutputStream.write(buffer, 0, bytesRead);
    }

    //System.out.println(entry.getName() + " added.");
    }
    finally {
    fis.close();
    }
    }

    //Copy original jar file to the temporary one.
    Enumeration jarEntries = jarFile.entries();
    while(jarEntries.hasMoreElements()) {
    JarEntry entry = jarEntries.nextElement();
    /*
    * Ignore classes from the original jar which are being replaced
    */
    String[] fileNameArray = fileNames.toArray(new String[0]);
    Arrays.sort(fileNameArray);//required for binary search
    if (Arrays.binarySearch(fileNameArray, entry.getName()) < 0) {
    InputStream entryInputStream = jarFile.getInputStream(entry);
    tempJarOutputStream.putNextEntry(entry);
    byte[] buffer = new byte[1024];
    int bytesRead = 0;
    while ((bytesRead = entryInputStream.read(buffer)) != -1) {
    tempJarOutputStream.write(buffer, 0, bytesRead);
    }
    } else if (!update) {
    throw new IOException("Jar Update Aborted: Entry " + entry.getName() + " could not be added to the jar" +
    " file because it already exists and the update parameter was false");
    }
    }

    jarUpdated = true;
    }
    catch(Exception ex) {
    System.err.println("Unable to update jar file");
    tempJarOutputStream.putNextEntry(new JarEntry("stub"));
    }
    finally {
    tempJarOutputStream.close();
    }

    }
    finally {
    jarFile.close();
    //System.out.println(srcJarFile.getAbsolutePath() + " closed.");

    if (!jarUpdated) {
    tmpJarFile.delete();
    }
    }

    if (jarUpdated) {
    srcJarFile.delete();
    tmpJarFile.renameTo(srcJarFile);
    //System.out.println(srcJarFile.getAbsolutePath() + " updated.");
    }
    }

    // Caller must close the InputStream when done
    public static InputStream getClassinputStream(String className, JarFile jar) {
    Enumeration jarEntries = jar.entries();
    while(jarEntries.hasMoreElements()) {
    JarEntry entry = jarEntries.nextElement();
    if (entry.getName().equals(className.replaceAll(“\\.”, “/”) + “.class”)) {
    try {
    return jar.getInputStream(entry);
    } catch (IOException e) {
    System.err.println(“Unable to get input sream for class ” + className);
    e.printStackTrace();
    return null;
    }
    }
    }
    return null;
    }

    }

      Carson G. Balzrette said:
      May 20, 2016 at 3:19 pm

      I just read your latest update and I really hate to see people with your talent and knowledge stop sharing it with the Java community. It is indeed a sad state when one individual either does not appreciate such talent and knowledge and hassles someone into stopping them. I am sorry for the communities loss and wish the particular individual would direct his energy and persistence at some of the web sites that are truly abusive and un-wanted and worthy of removal from the web. Thanks for all you have done for those of us who lack the abilities you guys apparently have. Best wishes.
      Regards,
      Carson G. Balzrette

      Mark Leone said:
      May 20, 2016 at 5:20 pm

      Ditto Carson’s comments.

      Regarding the JarUtil code, I can offer one more improvement, using the java.nio package , which was probably not available when the code was written.

      The line “tmpJarFile.renameTo(srcJarFile)” has an unfortunate side effect for me on Linux when running from a network share drive. It deletes the jar file instead of updating it. The API says that File#renameTo has implementation-dependent behavior.

      You can use java.nio.file.Files#move, which does not have implementation-dependent behavior. Replace “tmpJarFile.renameTo(srcJarFile);” with “Path path = Files.move(tmpJarFile.toPath(), srcJarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);”

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s