The lack of an effective modular programming model has frustrated Java developers since the Java platform's infancy. Until recently no single solution had stuck. In this article, Jeff Hanson explains why modularity is a next evolutionary step for the Java platform. He then surveys the two JSRs that propose to bring modularity to Java 7 -- JSR 291: Dynamic Component Support for Java SE and JSR 277: Java Module System. Both would bring the programming model popularized by OSGi to the Java platform, but they differ -- sometimes greatly -- in execution and philosophy.
The Java developer community has been seeking a modular component framework and model since Java first appeared in the mid 1990s. The JavaBeans framework was billed as a programming and deployment mechanism that would compete with ActiveX as a component technology and that could be consumed in component-based development environments, much like Visual Basic. JavaBeans, we were told, would be easily packaged using the JAR format and distributed and deployed wherever we wanted, fulfilling Sun's write once, run anywhere (WORA) promise.
As most of us know, it didn't work out that way. Many attempts have been made to solve the problem of modularity since that time (the list includes EJBs, EAR, WAR, Java applets, and the Java Network Launching Protocol) but each of them has soon encountered it own distribution and deployment woes.
This article discusses the latest attempts at making Java deployment artifacts and mechanisms modular and efficient, namely JSR 277: Java Module System and JSR 291: Dynamic Component Support for Java SE. I'll discuss the background of modular programming on the Java platform and why it is so important to Java developers today. I'll also provide an overview of both of the emerging approaches to modularity in Java 7, with some perspective on their inception and history to date in the Java Community Process. Finally, I'll walk through the high-level concepts of a modular programming system -- such as module definition, distribution format, and versioning -- and explain how JSR 277 and JSR 291 would handle them differently.
The long and winding road
Shortly after the JavaBeans framework was introduced, it became apparent that tools vendors were not joining the modularity quest fast enough, and that the AWT framework was not robust enough for Java to flourish on the desktop. Even when a particular vendor exploited AWT, JavaBeans, and JAR files in a way that showed promise, JAR and JRE dependency hell soon reared their ugly heads, bringing many of the horrors associated with DLL hell to the world of Java.
Then the quest for modularity was diverted to distributed systems and Web application environments, and this is where things got really interesting. The idea was to provide a framework for building distributed systems (or the components of such a system) according to specifications, with standard interfaces. Such systems could then be deployed to any Java-based environment.
This Java-and-WORA-based utopia would consist of systems built using best-of-breed components wired together to form complex and robust distributed applications. The Java 2 Platform, Enterprise Edition (J2EE) was formulated to meet these goals, along with the EJB, EAR, and WAR specifications and deployment mechanisms. Now a set of resources, classes, configuration files and the like could be implemented, packaged, and deployed to any standard J2EE application server. An EJB, EAR, or WAR could use any component or library available on the standard classpath. The quest was over -- or so it seemed.
Once components like EJBs, EARs, and WARs started making their way into real-world enterprise systems, a frustrating situation surfaced. Even though the ancillary frameworks and libraries needed by the components were in place at runtime, different versions of these frameworks and libraries (JAR files) were inevitably encountered. To deal with these versioning issues, components started to indicate the correct version of the ancillary JAR files in their deployment file. That worked fine until the application server or container included its own version of the files, because the server or container usually placed its JAR files closer to the start of the classpath search tree. All kinds of version-related problems began to arise as a result.
Compounding this confusion was possibility that a new version of a library or framework could be deployed to a system that was already in production. Most class loaders kept class definitions resident in memory as long as the JVM was running, and weren't able to load new versions of the classes. Therefore, to deploy a new version of a framework or library, the server's or container's JVM needed to be stopped and restarted. This resulted in downtime-related errors, frustrations, and unhappy users.
To solve this problem, server and container providers came up with hot deployment: specialized classloading mechanisms and plug-in frameworks that would allow the deployment of updated frameworks and libraries at runtime. Hot deployment is no trivial task, and even the most sophisticated of solutions are often fragile and bug-prone. Other problems surface when the classloading mechanism is tweaked, such as multiple versions of the same class existing in the same JVM at the same time, increased perm space, and so on.
Obviously, a standard, complete solution is needed to address the long-standing problem space of modularity.