Java resource loading

Anytime one needs to load Java resources (XML, jpeg, PDF, etc) from the classpath, using Class.getResourceAsStream or ClassLoader.getResourceAsStream is the way to go. However, there seems to be some confusion on how each one of these methods deals with the path in URL. I hope that this detailed experiment will serve as a helpful reminder of what the requirements are for both methods.

To make things more interesting I’ve put together a little program that should demonstrate the results on practice. I’ve covered three scenarios for where the file may reside in a typical Java application.

Assume that directory structure looks like this:

/
/rootFile.txt
/src/
/src/srcFile.txt
/src/com/letor/example/
/src/com/letor/example/packageFile.txt
  • rootFile.txt resides in the root folder
  • srcFile.txt resides in the src folder, will be included in the classpath (alternative to this is your typical cls folder
  • packageFile.txt lies in the same package directory as the class invoking the call

The program I wrote looks like this:

package com.letor.example;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ResourceLoader 
{
	private static Logger log_ = Logger.getLogger(ResourceLoader.class.getName());
	
	public static String[] FILE_NAMES = { "rootFile.txt",
										  "/rootFile.txt",
										  "srcFile.txt",
										  "src/srcFile.txt",
										  "/src/srcFile.txt",
										  "packageFile.txt", 
										  "com/letor/example/packageFile.txt",
										  "/com/letor/example/packageFile.txt" };
	
	public static void main(String[] args)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\n");

		try
		{
			for (String filename : FILE_NAMES)
			{
				// Class
				sb.append("Class\t\t");
				InputStream in = ResourceLoader.class.getResourceAsStream(filename);
	
				if (in == null)
				{
					sb.append("NO");
				}
				else
				{
					sb.append("YES");
					in.close();
				}
	
				sb.append("\t[").append(filename).append("]\n");
				
				// ClassLoader
				sb.append("ClassLoader\t");
				in = ClassLoader.getSystemResourceAsStream(filename);
				
				if (in == null)
				{
					sb.append("NO");
				}
				else
				{
					sb.append("YES");
					in.close();
				}
	
				sb.append("\t[").append(filename).append("]\n");
			}
		}
		catch (IOException ioe)
		{
			log_.log(Level.SEVERE, "Failed to load a resource", ioe);
		}
		log_.info(sb.toString());
	}
}

Running the program produces the following output. Class indicates Class.getResourceAsStream() call. ClassLoader indicates ClassLoader.getSystemResourceAsStream() method.

Class		NO	[rootFile.txt]
ClassLoader	NO	[rootFile.txt]
Class		NO	[/rootFile.txt]
ClassLoader	NO	[/rootFile.txt]
Class		NO	[srcFile.txt]
ClassLoader	YES	[srcFile.txt]
Class		NO	[src/srcFile.txt]
ClassLoader	NO	[src/srcFile.txt]
Class		NO	[/src/srcFile.txt]
ClassLoader	NO	[/src/srcFile.txt]
Class		YES	[packageFile.txt]
ClassLoader	NO	[packageFile.txt]
Class		NO	[com/letor/example/packageFile.txt]
ClassLoader	YES	[com/letor/example/packageFile.txt]
Class		YES	[/com/letor/example/packageFile.txt]
ClassLoader	NO	[/com/letor/example/packageFile.txt]

Now let’s go over each one of the lines and figure out what is happening

Class		NO	[rootFile.txt]
ClassLoader	NO	[rootFile.txt]
Class		NO	[/rootFile.txt]
ClassLoader	NO	[/rootFile.txt]

In all four cases files are not visible to the class loader since the root directory is not in the classpath.

Class		NO	[srcFile.txt]
ClassLoader	YES	[srcFile.txt]

Class does not see this file since it looks in the same directory where it resides. ClassLoader, however starts at the root of the classpath and finds the file.

Class		NO	[src/srcFile.txt]
ClassLoader	NO	[src/srcFile.txt]
Class		NO	[/src/srcFile.txt]
ClassLoader	NO	[/src/srcFile.txt]

In all of these cases the file is not found. Notice that prepending slash to the path does not help.

Class		YES	[packageFile.txt]
ClassLoader	NO	[packageFile.txt]

This is the exact opposite of what we saw when looking for srcFile.txt. Here the Class finds the file since it is in the same package, but ClassLoader fails

Class		NO	[com/letor/example/packageFile.txt]
ClassLoader	YES	[com/letor/example/packageFile.txt]

The last four cases are the most interesting. Notice the inverse relationship. Here the Class fails to find the file since it starts looking from its own directory. ClassLoader looks from the classpath root and easily finds the file

Class		YES	[/com/letor/example/packageFile.txt]
ClassLoader	NO	[/com/letor/example/packageFile.txt]

Finally Class figures out what to do, since we’ve specified a slash. Now it knows to go back to the beginning of the classpath. ClassLoader is less lucky, that slash confuses it.

I hope that this has been as educational for you as it was for me. Let’s sum all of this up.

Class.getResourceAsStream() will always look in its own package directory first. The only way to tell it look elsewhere is to provide it with an absolute path

ClassLoader.getSystemResourceAsStream() will always start looking at the root of the classpath, but do not confuse it by a forward slash.

“Pragmatic Project Automation”

“Pragmatic Project Automation” does a good job of introducing us to the world of continuous software integration. It covers all aspects of the software lifecycle, mainly focusing on the build, deployment and monitoring. You will learn how to organize your software release process so as to utilize a number of automatic tools, services and techniques.

The end result should be a process where a large number of human errors during deployment would be caught or avoided, gaining better confidence in what has been released.

Technologies used in this book are: Java, Ant, CruiseControl, scripting, JUnit. There is talk of Maven, but I guess that it was not yet as prominent in 2004 as it is today. The same techniques can easily be applied to .NET, PHP, Python or any other development environment.

Since most examples assume a website for a project, I wish that there would be a section on how a client-server app with a database would fit this model.

One thing that I find awkward is the choice of Version Control, it is still CVS. This book was published in 2004, when SVN has already showed its superiority over CVS.

This book does contain everything one need to know to start using project automation at your shop. I would recommend a place on developer’s bookshelf for this book.

P.S. This book is still not available on Kindle. :(

Java @Override annotation usage

Since the start of using annotations I was not a big fan of using @Override annotation. It seemed to have happened overnight, where after a new version of Eclipse was installed I was presented with 400+ warnings telling me to put in the dreaded overrides.

I do think that this seems unnecessary since it clutters the code a lot. But having lots of unresolved warnings in your IDE is even worse. Yes, I know that the compiler warnings can be fined tuned, but there might be a reason this warning comes on by default. So what is it?

As it turns out there are a few benefits. One is that the compiler will perform the check for you if you’ve specified the @Override annotation and will ensure that you are in fact overriding a method. This means that you no longer will have errors in your code where you’ve mistyped method name or have a slightly different signature. This is quite a benefit, since finding some of these errors can turn out to be quite time consuming.

The second benefit is that it will indicate to a fellow developer that this method indeed overrides (or implements) another method.

Well, there you have it. I am changing my stance on this subject and am willing to give the @Override a try. 

“Failed to load JavaHL Library”

One may get the following error message when running Subclipse plugin in Eclipse on a Mac OS X.

Failed to load JavaHL Library.
These are the errors that were encountered:
no libsvnjavahl-1 in java.library.path
no svnjavahl-1 in java.library.path
no svnjavahl in java.library.path
java.library.path = .:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

SVN will still work by some sort of magic, but the official reason for you getting this error is that Eclipse can not find the JavaHL library (under /Library/Java/Extensions). JavaHL is the Java language binding for the Subversion API.

The most painless option is to re-install Subversion as part of the binary package from CollabNet. This will also install JavaHL.

Unfortunately this download requires a registration. This registration is rather painful as well.

Notice that the link for “Snow Leopard” is not the first one. This is a surprising choice, since that is the most recent version of the OS to date.

Make sure to update your .profile file with the following line. Otherwise we will still be using the old version of Subversion.

#!/bin/sh
export PATH=/opt/subversion/bin:$PATH

After you’ve updated your .profile file, close your Terminal and open a new one. Make sure that you are using the latest version.

sturgeon-2:~ dmitry$ which svn
/opt/subversion/bin/svn
sturgeon-2:~ dmitry$ svn --version
svn, version 1.6.15 (r1038135)
   compiled Nov 29 2010, 16:11:54

Copyright (C) 2000-2009 CollabNet.
Subversion is open source software, see http://subversion.apache.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

Restart your Eclipse and you are done. No more warnings. You can verify that you’ve got the right version of JavaHL installed by going into Preferences->Team->SVN. Under “SVN Interface” you will see the version of JavaHL that you are using.