More on Jar Hell

Regarding my prior post on Jar Hell, a colleague asked me to elaborate on the problems I experienced and why .NET would not have these problems.

.NET basics

First thing: .NET has a version-aware classloader and strongly-named assemblies. This means when I build a .NET app against an assembly, the app runs against that assembly, and that assembly only, unless I explicitly override that.

Also, .NET adds the concept of a global assembly cache, the GAC, into which fundamental, re-usable libraries can be placed. (The GAC gets over-used in my opinion. Too many people think their libraries are fundamental and machine wide, when they are clearly not. In any case, the GAC mechanism has its uses.)

Java classloading

I am not a Java classloader expert, so take all of what follows with a grain of salt. In practice, the way it works in Java is the Java classloader loads classes from the classpath. The Java application environment also has well-known directories from which libraries may be loaded. The lib and ext directories in the JDK/JRE, for example.

The differences

Beyond the basics, when you have an "app container" - say JBoss,Tomcat, or WebSphere - they add their own classloading rules, and special directories from which jars will be loaded.

This is one fundamental difference between Java and .NET. In .NET there is one way to find and load assemblies. It's done by Fusion. Essentially there are two places to look for assemblies: in the app's home directory (or a "special" bin directory for ASP.NET apps), or in the GAC. In Java, there are a myriad of ways to find jars, and a variety of ways to load them. Special directories abound.

The setting for JBoss+Tomcat - where I can tell it to use either the JBoss classloader behavior or the Tomcat classloader behavior (go here and scroll down half the page) - the very existence of this switch illustrates the problem. There is no such switch in .NET.

Beyond that though, is the proliferation of special directories. library directories for the JVM, for the web application container, for the uber application container. extension directories, and on and on. In .NET, there are two places to look. Not seventy two.

Next up: component libraries. On the Java side, some of the ones I used were: servlet.jar, jaxrpc.jar, log4j, xerces (an XML library). Each of these is independently versioned. But, the Java classloader doesn't care, doesn't respect versions. The classloader just scans the classpath and the special directories and loads a class with a given fully-qualified name.

If I build and test against Xerces 1.4.4, and then drop by app into a container that has a different version of Xerces, the Java classloader won't complain. Won't raise an eyebrow.

A perfect example of this is the SAX problem I had. The BEA jrockit JVM ships a version of SAX, in one of its special directories. But so also (apparently) does AXIS, or JBoss, or something else in the chain. The error I saw was the result of the "wrong" SAX library being loaded when JBoss/Tomcat/AXIS was run under BEA's JVM.

Another example is the NoSuchMethodError I encountered while running two distinct versions of AXIS within Tomcat (within JBoss). At runtime, the classloader picked up the wrong version of the library - presumably the 1.1 version and not the 1.2 version my app was built against.

A final example, although not nearly as serious, is the ImageIO thing and the support for BMP. My WordML app was depending on Java 1.5 ImageIO function, but it ran against Java 1.4 ImageIO libraries. It was up to me as a developer to either handle the non-performance, or check whether the Java engine was 1.5 or below. This is merely a hassle, because I could handle it in my own code. The other examples were not even my code.

In .NET this entire class of errors doesn't exist. You have to have the exact right assembly - the same one you built against, or your app won't run. This means, no accidental or inadvertent class swapping. You can make this even more bulletproof with cryptographically strong names for assemblies. This means, no malicious class-swapping. (Like, stubbing out a license enforcement class by playing classpath games. I've proven this works with major app servers).

Is he done yet?

Yeah, almost. I'm wrapping up here.

What this all means is .NET developers can basically ignore classloading and versioning issues. With .NET, essentially, you copy in the app's files (including library files), and the app is installed. With Java, you copy in the JARs, and then test, and test, and test. And you're never really sure it's going to work, because buried deep inside some routine, somewhere, maybe not even in your code, is a call out to log4j or xerces or some other library that is out-of-version.

A bunch of people have responded to my previous post and said, "I feel your pain". Another set of people responded and said "so what else is new? it's been like this for years." That is very surprising to me. I am not a Java developer by profession. It's more of a hobby ;). So I can't believe people really deal with this every day. It's so... 1997. It's like COM and "DLL Hell" all over again.

I am not able to assure you that you'll never have versioning issues with .NET. Just that I think .NET's approach is so much more ... usable.

Update 611pm US Eastern: removed incorrect reference