Determine which JAR file a class is from

JavaJarClassClassloader

Java Problem Overview


I am not in front of an IDE right now, just looking at the API specs.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
    URL jar = src.getLocation();
}

I want to determine which JAR file a class is from. Is this the way to do it?

Java Solutions


Solution 1 - Java

Yes. It works for all classes except classes loaded by bootstrap classloader. The other way to determine is:

Class klass = String.class;
URL location = klass.getResource('/' + klass.getName().replace('.', '/') + ".class");

As notnoop pointed out klass.getResource() method returns the location of the class file itself. For example:

jar:file:/jdk/jre/lib/rt.jar!/java/lang/String.class
file:/projects/classes/pkg/MyClass$1.class

The getProtectionDomain().getCodeSource().getLocation() method returns the location of the jar file or CLASSPATH

file:/Users/home/java/libs/ejb3-persistence-1.0.2.GA.jar
file:/projects/classes

Solution 2 - Java

Checkout the LiveInjector.findPathJar() from Lombok Patcher LiveInjector.java. Note that it special cases where the file doesn't actually live in a jar, and you might want to change that.

/**
 * If the provided class has been loaded from a jar file that is on the local file system, will find the absolute path to that jar file.
 * 
 * @param context The jar file that contained the class file that represents this class will be found. Specify {@code null} to let {@code LiveInjector}
 *                find its own jar.
 * @throws IllegalStateException If the specified class was loaded from a directory or in some other way (such as via HTTP, from a database, or some
 *                               other custom classloading device).
 */
public static String findPathJar(Class<?> context) throws IllegalStateException {
	if (context == null) context = LiveInjector.class;
	String rawName = context.getName();
	String classFileName;
	/* rawName is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ {
		int idx = rawName.lastIndexOf('.');
		classFileName = (idx == -1 ? rawName : rawName.substring(idx+1)) + ".class";
	}

	String uri = context.getResource(classFileName).toString();
	if (uri.startsWith("file:")) throw new IllegalStateException("This class has been loaded from a directory and not from a jar file.");
	if (!uri.startsWith("jar:file:")) {
		int idx = uri.indexOf(':');
		String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx);
		throw new IllegalStateException("This class has been loaded remotely via the " + protocol +
				" protocol. Only loading from a jar on the local file system is supported.");
	}

	int idx = uri.indexOf('!');
	//As far as I know, the if statement below can't ever trigger, so it's more of a sanity check thing.
	if (idx == -1) throw new IllegalStateException("You appear to have loaded this class from a local jar file, but I can't make sense of the URL!");

	try {
		String fileName = URLDecoder.decode(uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name());
		return new File(fileName).getAbsolutePath();
	} catch (UnsupportedEncodingException e) {
		throw new InternalError("default charset doesn't exist. Your VM is borked.");
	}
}

Solution 3 - Java

Use

String path = <Any of your class within the jar>.class.getProtectionDomain().getCodeSource().getLocation().getPath(); 

If this contains multiple entries then do some substring operation.

Solution 4 - Java

private String resourceLookup(String lookupResourceName) {

    

    try {
       
        if (lookupResourceName == null || lookupResourceName.length()==0) {
            return "";
        }
        // "/java/lang/String.class"
        
        // Check if entered data was in java class name format
        if (lookupResourceName.indexOf("/")==-1) {
            lookupResourceName = lookupResourceName.replaceAll("[.]", "/");
            lookupResourceName =  "/" + lookupResourceName + ".class";
        }

        URL url = this.getClass().getResource(lookupResourceName);
        if (url == null) {
            return("Unable to locate resource "+ lookupResourceName);
           
        }

        String resourceUrl = url.toExternalForm();

        Pattern pattern =
            Pattern.compile("(zip:|jar:file:/)(.*)!/(.*)", Pattern.CASE_INSENSITIVE);
       
        String jarFilename = null;
        String resourceFilename = null;
        Matcher m = pattern.matcher(resourceUrl);
        if (m.find()) {
            jarFilename = m.group(2);
            resourceFilename = m.group(3);
        } else {
            return "Unable to parse URL: "+ resourceUrl;
           
        }

        if (!jarFilename.startsWith("C:") ){
          jarFilename = "/"+jarFilename;  // make absolute path on Linux
        }

        File file = new File(jarFilename);
        Long jarSize=null;
        Date jarDate=null;
        Long resourceSize=null;
        Date resourceDate=null;
        if (file.exists() && file.isFile()) {
            
            jarSize = file.length();
            jarDate = new Date(file.lastModified());
            
            try {
                JarFile jarFile = new JarFile(file, false);
                ZipEntry entry = jarFile.getEntry(resourceFilename);
                resourceSize = entry.getSize();
                resourceDate = new Date(entry.getTime());
            } catch (Throwable e) {
                return ("Unable to open JAR" + jarFilename + "   "+resourceUrl +"\n"+e.getMessage());
               
            }
           
           return "\nresource: "+resourceFilename+"\njar: "+jarFilename + "  \nJarSize: " +jarSize+"  \nJarDate: " +jarDate.toString()+"  \nresourceSize: " +resourceSize+"  \nresourceDate: " +resourceDate.toString()+"\n";
           
           
        } else {
            return("Unable to load jar:" + jarFilename+ "  \nUrl: " +resourceUrl);
          
        }
    } catch (Exception e){
        return e.getMessage();
    }


}

Solution 5 - Java

With Linux, I'm using a small script to help me find in which jar a class lies that can be used in a find -exec:

findclass.sh:

unzip -l "$1" 2>/dev/null | grep $2 >/dev/null 2>&1 && echo "$1"

Basically, as jars are zip, unzip -l will print the list of class resources, so you'll have to convert . to /. You could perform the replacement in the script with a tr, but it's not too much trouble to do it yourself when calling the script.

The, the idea is to use find on the root of your classpath to locate all jars, then runs findclass.sh on all found jars to look for a match.

It doesn't handle multi-directories, but if you carefully choose the root you can get it to work.

Now, find which jar contains class org.apache.commons.lang3.RandomUtils to you un-mavenize your project (...):

$ find ~/.m2/repository/ -type f -name '*.jar' -exec findclass.sh {} org/apache/commons/lang3/RandomUtils \;

.m2/repository/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar
.m2/repository/org/apache/commons/commons-lang3/3.6/commons-lang3-3.6.jar
.m2/repository/org/apache/commons/commons-lang3/3.6/commons-lang3-3.6-sources.jar
$

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionWalter WhiteView Question on Stackoverflow
Solution 1 - JavaChandra PatniView Answer on Stackoverflow
Solution 2 - JavanotnoopView Answer on Stackoverflow
Solution 3 - JavaAbhishek ChatterjeeView Answer on Stackoverflow
Solution 4 - JavaDonView Answer on Stackoverflow
Solution 5 - JavaMatthieuView Answer on Stackoverflow