Picking a Winner: .NET vs. J2EE
March 2001
Although both frameworks stand on a foundation of programming languages, object models and virtual machines, they are strikingly different when you consider the design goals of their runtime environment.
by Jim Farley
The Java 2 Enterprise Edition (J2EE) framework has captured significant mindshare among online application developers and their organizations, as a powerful, portable and widely-supported application development environment. Microsoft's announcement of .NET (dot-NET) has captured the attention of just as many, since it represents the future direction of all things Microsoft, from desktop applications like Microsoft Office to development tools like Visual Studio to back-end servers like SQL Server, and everything in between.
Opinions on the ultimate outcome of this framework competition range far and wide—zealots on either side of the "demilitarized zone" claim emphatically that either Sun and J2EE or Microsoft are doomed to fail. Some of the more balanced analysts predict a near-term stalemate, with J2EE and dot-NET eventually claiming a roughly equal share of the application development market. This prediction seems the most sensible, given the two forces facing off—J2EE with a significant amount of development inertia and a broad array of tool and application vendors behind it, and dot-NET with the marketing influence of Microsoft fueling its spread.
Regardless of the long-term result, it seems unavoidable that technology managers, strategists and software developers will have to contend with both of these frameworks for some time to come, and I've tried to provide some guidance to the development community around this "framework divide." Whether you're part of an organization committed to either J2EE or Microsoft platforms, trying to assess the costs and benefits of sticking with your current framework, jumping to the other or attempting to support both; or if you're an organization about to venture into a development effort and attempting to pick the most suitable path, I've attempted to lay out the two frameworks as clearly and unambiguously as possible.
(Half-)Sisters Under the Skin
It's important to acknowledge that despite what the best marketing minds at Sun and Microsoft might lead you to believe, dot-Net and J2EE are surprisingly similar from a pragmatic standpoint. Both offer a server- and client-side component model for assembling enterprise applications, APIs that allow you to create both fat and thin user interfaces for these applications, and specifications and corresponding APIs that allow you to access critical services such as transactional data sources, directory services and remote objects.
At a lower level, the two frameworks are both based on a virtual machine architecture aimed at portability (though the type of portability each strives for is a critical difference between the two). Also, J2EE and dot-NET are comparable in some areas in terms of feature support and architecture.
Don't Know Beans?
When J2EE was introduced by Sun, it subsumed, to a large degree, the JavaBean and Enterprise JavaBean component models. JavaBeans are the meat-and-potatoes Java components, with well-defined properties, event-handling provisions, persistence through Java serialization and introspection, in addition to all of the basic services provided by the Java runtime environment, like memory management, "sandbox" security and the core Java APIs. Enterprise JavaBeans are the distributed equivalent: remotely-accessible components that operate within an environment that provides application security, distributed transaction support, richer persistence management facilities, life cycle management and resource pooling.
The dot-NET framework, meanwhile, subsumes Microsoft's workhorse COM and COM+ component models. These component models are replaced, in a sense, by the new component architecture of the dot-NET framework. The Common Language Runtime (CLR) now provides "sandbox" security and basic services like garbage collection, similar to the Java Virtual Machine, which were not available previously in the COM and COM+ environments. It also extends the component model itself with features such as multiple interface inheritance, extensible meta-data and a new delegate model. So the CLR defines a new, richer component model, which COM and COM+ components can be mapped into, and which is accessible by COM and COM+ components, but it is separate and distinct from COM. This has fueled doubts as to whether COM and COM+ will continue as "first-class" supported programming models in Microsoft's product line (See Don Box's article, "House of COM," in the December 2000 issue of MSDN magazine on how the new CLR environment differs from the COM environment).
Microsoft's support for components has a long history, driven in part by the fact that it provides the principal visual IDE for Microsoft developers (Visual Studio and its predecessors). Microsoft has had the luxury of taking its component model through several iterations of improvements, despite the subsequent churn inflicted on its developers. Java's component model inherited the institutional learning that came before it, but improvements to the Java component model are handicapped to some degree by its dependence on the Java language and the open nature of Java's evolution.
The Skinny on Fat Clients
For "fat" GUI clients, J2EE offers the Java Swing API, with a palette of standard GUI JavaBean components that can be assembled programmatically, or pulled into visual GUI design tools like WebGain (formerly Symantec) Visual Cafe, Borland JBuilder, IBM VisualAge for Java and so on. For thin-client development, the servlet and JavaServer Pages (JSP) standards provide ways to build HMTL, WML, XML and other thin interfaces on top of a Java middle tier. Java servlets and JSPs are supported by a number of application server vendors, both commercial (such as BEA WebLogic, iPlanet, IBM WebSphere and SilverStream), and open source (for instance, Apache's Jakarta project, Enhydra and Resin).
In the past, Microsoft has offered "fat" client support through its Microsoft Foundation Classes (MFC) API, but dot-NET offers a new set of components: Windows Forms. Windows Forms fill the same functional role as MFC, but they are plugged into the new dot-NET runtime framework and component model. Similarly, on the thin-client front, ASP.NET is the dot-NET successor to Active Server Pages (ASP): Functionally, it fills the same role as a thin-client (HMTL, WML or XML) paradigm for applications, but ASP.NET pages participate in the new CLR. In this case, that means that code snippets and component references can be added to pages using any CLR-compliant language, and ASP.NET pages are compiled into the IL-based CLR runtime, rather than interpreted, as ASP pages are.
Data Sources and Directory Services
J2EE and dot-Net both support, in different ways, a few strategic APIs and services such as access to data sources and directory services, and general-purpose XML support for applications. Access to data sources within the Java environment is handled through JDBC, an RDBMS-independent Java specification for interfacing with relational databases. Specific vendor RDBMSs are supported through JDBC drivers, which are available for all of the popular commercial and open-source database options.
JDBC is a fairly low-level API, in that it models database access in terms of Connections, Statements and ResultSets. JDBC 2.0 and its standard extensions add useful services such as connection pooling and support for Open XA distributed transactions—J2EE 1.2.1 (the current industry-supported version of the standard) doesn't mandate these extensions, but J2EE 1.3 (currently in development) does. Higher-order data access services are provided within J2EE by entity EJBs and the persistence management services of EJB containers. Commercial third-party object-relational tools, such as Sun's Java Blend or WebGain's TopLink, are also available, but aren't strictly part of the J2EE framework.
Directory services are accessed in Java using Java Naming and Directory Interface (JNDI), and a different object model involving Names, Contexts and Attributes, which represent name-to-object mappings within a naming or directory service, and meta-data attributes associated with these objects within a directory. Access to various types of directory services, such as LDAP, Novell NDS, NIS, and more recently with DNS and Directory Services Markup Language (DSML) XML sources, are accomplished through JNDI providers, which are analogous to JDBC drivers.
Microsoft's data-oriented services and APIs have had a bumpy past. Early efforts stemming from OLE and ODBC led to Data Access Objects (DAO), which led to the ActiveX Data Objects (ADO) API. ADO sits as a peer to both JDBC and JNDI—data access is modeled as Connections, Commands and Recordsets, and ADO provides unified access to both relational data sources and directory services. ADO has offered some features, such as record navigation and "offline" rowset access, that weren't available in JDBC until the 2.0 version of that specification. On the negative side, ADO rides on top of the COM-based OLE DB and ODBC interfaces, which severely limits its interoperability and scalability.
In recognition of this, dot-NET replaces ADO with ADO.NET. (Microsoft's earlier term for the dot-NET data access components was ADO+; It's unclear whether this change in nomenclature will stick or not, but in this article I'll use ADO.NET, instead of ADO+, for simplicity). ADO.NET is a new data access API that replaces the COM-based marshalling of data with XML data transmission from data source to the ADO.NET data component. The API model is essentially the same—Recordsets are replaced with DataSets, Commands are replaced with DataSetCommands and so on. In the dot-NET framework, there is also the new System.DirectoryServices package, which provides interfaces specifically designed for accessing directories. The big difference in ADO.NET (from both its predecessor, ADO, and its competing APIs, JDBC and JNDI) is that the data transmission format from source to DataSet is XML, which allows ADO.NET to treat anything that emits XML in the ADO.NET schema as a data source. Contrast this with JDBC and JNDI, where the API allows the driver or provider to use whatever exchange protocol is appropriate (for example, an Oracle driver can use a JNI interface to the OCI libraries, or it can implement the SQL*net or NET8 protocols directly in Java). It would be possible to implement a JDBC driver, for example, that communicates with a data source using XML, which would open up the same types of portability potential that ADO.NET promises. But the JDBC API doesn't expose this potential data access paradigm to the application layer directly—you would still need to interact with the data in the form of ResultSet objects.
XML is a more accessible and portable data exchange protocol, and support for it within ADO.NET is good for developers and vendors alike. It opens up interesting possibilities in terms of exporting virtual "data sources," or components themselves being able to serve as a data source for each other. However, in practice, high-volume data exchanges would probably be better served by a more compact, efficient protocol dictated by the data source engine. XML is well-structured and humanly readable, but it's rather fat as data protocols go.
General-Purpose XML Support
Both Java and dot-NET provide the now-expected basic XML support, which includes APIs for Document Object Model (DOM) generation and parsing. The Java API for XML Processing (JAXP) is a Java extension API that supports DOM and SAX (Simple API for XML) models for XML sources, and provides a plug-in architecture for different parsing engines. Note that I didn't mention J2EE here. J2EE 1.2.1 doesn't dictate that an XML API be present in the environment—a requirement that wasn't added until J2EE 1.3. Of course, this is largely academic, since virtually any Java application server has already, or is planned to have, a Java XML API and parser bundled. But the lack of a requirement for an XML API in the framework is a sign of the level of XML adoption present in J2EE.
This isn't to say that Sun and the Java community have been slouches in the XML arena. Quite the contrary. XML support in the Java environment is plentiful and timely: New XML-related standards are almost ubiquitously implemented in Java, and typically before other languages. More recently, Sun has announced plans to define a standard Java API for XML Messaging (JAXM), which uses ebXML messaging to provide XML request and response services for the Java environment, and the Java API for XML Data Binding (JAXB), which uses both XML Schema and XML Binding to define a standard Java API for the marshalling and unmarshalling of Java objects into and out of XML document models. At the time of this writing, these APIs aren't formal elements of the J2EE framework and plans to incorporate them into its application, UI and data access services haven't been announced.
Microsoft has also embraced XML vigorously. At the ground level, the company provide its MSXML API for DOM and SAX parsing and XML generation. More importantly, XML is an underlying feature of dot-NET at many layers. We've already discussed how ADO.NET uses XML for its underlying data exchange protocol. In general, dot-NET components use XML by default for data representation of various kinds. An especially interesting aspect of the dot-NET architecture is the ability for dot-NET Web services to easily export an XML interface, in the form of SOAP, across the network. (See my article on the O'Reilly Web site about the pros and cons of SOAP as a data exchange and RPC mechanism.)
This provides for some interesting possibilities: Clients of these SOAP-accessible Web services can take many forms, from client browsers to mobile devices to other Web services (implemented in dot-NET or in other frameworks). Again, the practicalities of having an XML parser in the mix, and the subsequent reduction in transaction rates, may keep you from using SOAP interfaces to dot-NET components in high-volume, high-scalability situations. But it's still nice to know that level of flexibility is there if you need it.
Where the Code Hits the Road
Formal deployment schemes are an attempt to reduce the confusion of loosely-defined dependencies between libraries, classes and media assets sitting on local and remote file systems, and "component versioning hell."
In J2EE, applications are composed of modules, which are collections of interrelated components, which come in three major flavors: Web components, EJB components and Java "clients" (which includes both application clients and applet clients). Each type of component is managed at runtime by a corresponding container type that provides it with services and APIs (for example, Web components have the JSP and Servlet APIs available through their container, while EJB components have access to the full EJB API and its security, life cycle and transactional services).
The dot-NET environment proposes an analogous application assembly and deployment scheme. Dot-NET applications are built up from assemblies, which contain EXE and DLL files that contain compiled dot-NET components. Assemblies can also contain other required resources, like media assets, support code or classes, and so on. Assemblies can be packaged in the form of Windows Installer files (.msi), Windows CAB files (.cab), or they can be dynamically downloaded by a Web application (similar to the way Java applet context and browser plugins are currently downloaded). Dot-NET assemblies can be deployed as standalones, or they can be grouped into an application.
Dot-NET defines an XML-based configuration scheme, where interassembly dependencies, security settings and resources are defined. Creation of DLLs, EXEs, resource files, configuration files and so on can be accomplished using command-line tools provided in the dot-NET framework SDK, or graphically through the upcoming dot-NET version of Microsoft Visual Studio (both of which are available in beta form at the time of this writing).
J2EE also makes use of XML deployment descriptors for modules and applications, in order to provide configuration information to the runtime J2EE environment. This configuration information includes a manifest of the contents of the module, component dependencies, details about required external resources, and security-related roles and access rights. Although the core Java environment supports command-line editing, a number of J2EE tool and application server vendors provide deployment tools that make point-and-click work out of the generation of XML deployment descriptors and the assembly of JavaBeans, servlets, JSPs and EJBs into JAR files, and the actual deployment of these modules and applications into running application servers, local or remote.
This leads to another distinction between dot-NET and J2EE application deployment. Deploying a given J2EE component within a particular J2EE server often involves, in addition to modifying the deployment descriptors, the generation of vendor-specific support classes for your components. This complicates matters with regards to tying application servers to integrated development environments. Dot-NET, as a single-vendor platform and framework, doesn't have to concern itself (or you) with this issue, since Microsoft provides both the development and deployment tools.
Dot-NET also doesn't make the same clear distinctions between different types of assemblies as J2EE does. There are clear application types (Web-based ASP.NET applications where ASP pages interact with back-end CLR components, or Windows Forms applications where the GUI is desktop Windows components interacting with CLR components), but the components themselves aren't categorized by service-levels—it's assumed that all components can have access to all of the services and APIs of the dot-NET environment. Since Microsoft is the sole provider of dot-NET implementations, it doesn't have to include such multivendor considerations in the architecture.
Critical Differences
I've outlined some subtle differences between today's dot-NET framework and J2EE framework, and so far the two frameworks have been fairly comparable on a pragmatic, functional level. However, there are critical differences between the two frameworks at a number of levels.
Dot-NET and J2EE both stand on a foundation of programming languages, object models and virtual machines. The most striking difference between the two frameworks is the design goals of their runtime environment and how these support very different programming and deployment schemes. The cliché developing in the community around this point is that "Java is language-specific and platform-independent, and dot-NET is language-independent and platform-specific." This cliché is in many ways an oversimplification since numerous J2EE implementations aren't entirely cross-platform. Microsoft has made some preliminary attempts to make dot-NET cross-platform, such as submitting the CLR specification and the C# language to the European Computer Manufacturers Association for public standardization, as well as announcing versions of the CLR for a variety of non-PC devices, including the Linux platform (if there is a market for it). The cliché remains fairly accurate, though, because the J2EE specification is fundamentally cross-platform and the full dot-NET story incorporates Windows as a central piece.
Figure 1. Competing Development Models
Java's unified programming environment is platform-independent; XML-enabled dot-NET is language-independent.
J2EE is a framework that evolved out of the core Java environment, which is founded on the Java language, the Java Virtual Machine (JVM) and the Java core APIs. The Java programming model calls for class descriptions written in Java to be compiled into platform-independent bytecodes as defined by the Java Virtual Machine specification (Figure 1). These bytecodes are then interpreted and executed by an implementation of the JVM targeted for the particular platform at hand—Solaris, Windows, Linux, AIX and so on. The JVM defines and provides a standard runtime environment that provides memory management and security. Alternatively, Java code (or the resulting Java bytecodes) can be compiled to a native executable for a specific platform, or runtime compiled to native code by a just-in-time (JIT) compiler. So, development in J2EE is meant to be done in Java, resulting in class bytecodes that can be run on any platform with a JVM or a Java-to-native compiler. Access to objects and components written in other languages is possible, using the Java Native Interface (JNI) and CORBA, but these are largely ancillary to the core development and runtime model. In both cases, these are also rather complicated APIs to master, which in part hinders their broad adoption.
Microsoft's dot-NET framework is based on its Common Language Runtime (CLR), which is composed of a specification for language-independent intermediate language (IL) code and a runtime that provides memory management, security and so on. This may seem analogous to the Java runtime architecture, but the difference is that code targeted for the dot-NET framework can be written in any language that supports the CLR's core component model. A compiler must be developed which compiles the code into IL—once there, the objects and components can run side-by-side and interact with components written in other languages and compiled into the CLR. Blocks of IL code are JIT-compiled into native Windows code, or your whole application and assembly can be compiled once into a native Windows DLL or EXE as shown in Figure 1.
The interesting thing about this difference in the two frameworks, and the reason it's a critical differentiator, is that this relatively low-level design feature strikes right at each framework's strategic direction. Java is designed to be a unified programming model that is platform- independent. Sun would say (and has, in different words): Once you have everything in the Java environment and the Java Virtual Machine, you gain tremendous flexibility in choosing tools and off-the-shelf components at development time, platforms at deployment time and interservice connections and live code sharing at runtime.
Dot-NET, on the other hand, is a unified platform (Windows) that is language independent and deeply XML-enabled. Microsoft might tell you: Once you have everything in the Windows platform and the Common Language Runtime, you gain tremendous flexibility in the ability to choose the development language that's right for you at development time, and in interconnecting components and Web services at runtime, either at the component and object level using the CLR, or in a looser sense using SOAP and XML. And deployment time decisions are simplified because everything is integrated into the Windows environment.
We'll return to the XML issue in a moment, but the language and platform distinction is critical. And there are chinks in the armor on either side of this architectural battle.
Java's true potential is realized by a development team and its surrounding organization only when a critical mass of development (or all of it) is done in Java. If, for some reason (legacy system, third-party requirements, component availability), a subset of a given system falls outside of the Java environment, things get complicated, and you have to turn to CORBA or JNI or other ways to bridge the gap.
This language-centric approach is also limiting strategically. It's easy to imagine some powerful improvements to the Java core environment that would be best accomplished by making changes to the syntax of the Java language, and these changes are very difficult to make because there is a vertical dependence, from the JVM to the J2EE application services, on the fact that everything happens in Java bytecodes.
A good example is the explicit declaration of properties on JavaBeans. Given Java's preexisting syntax, the JavaBeans specification was defined so that component properties are implicitly defined by the presence of methods of a particular signature (setXXX and getXXX) on the bean's Java class. A better solution would be to have a way to explicitly declare properties, keeping the method namespace independent. Doing this, however, would require altering the syntax of the Java language to include a scheme for declaring properties explicitly, and changing the Java language itself would cause changes to ripple upwards through the APIs and specifications, and downwards into the JVM in some cases. Given the success of Java and the number of vendors coding to its specifications, this would be a herculean task in concensus-building and making provisions for migration and backwards-compatibility. Ironically, it's analogous to the woes that Microsoft faced when it attempted to eliminate the DOS kernel from its OS.
Microsoft dot-NET, on the other hand, realizes its true potential only if it's necessary and prudent to create and manage components developed in multiple languages, and do it all on Windows. On the face of it, being able to use the language of your choice (from the set that supports MSIL compilation, that is) seems like a valuable possibility—if I don't know C# or C++ or Java, I can stay productive and write code in what I know, instead of starting a new learning curve. This is appealing for both developers and managers alike, but the CLR achieves somewhat less than this in reality.
You can't write standard, ANSI-compliant C++ or standard COBOL or Eiffel and make it work in the CLR sandbox. To make your code accessible to other CLR components, you have to write your code in Managed Extensions to C++, Eiffel# or COBOL#, which are syntactic variants of the original languages that they share names with. These variants include syntax extensions that are required in order to provide all of the component meta-data that the CLR requires, which can't be supported by the standard language. This means two things: there is still a learning curve to be climbed (although probably not a large one) in order to program to dot-NET's CLR, and the code that you write for dot-NET won't be understood by the standard compilers or interpreters for these languages.
As an example, the following is a class written in Managed Extensions for C++:
#using <mscorlib.dll>
using namespace System;
namespace SDExample {
__gc public class SimpleComponent {
private:
String* mName;
public:
SimpleComponent() {
mName = new String("anonymous");
}
__property int get_Name()
{ return mName; }
};
};
The red areas show the syntax elements that aren't standard C++. The "#using" preprocessor directive declares a Windows DLL that this component requires, the "__gc" tag marks this class as a CLR component eligible for memory management services, and the "__property" tag declares a property of the component. A developer still needs to learn the new component model of the CLR, and learn how the model impacts her "home" language, before she can write code for dot-NET. And ANSI-compliant third-party C++ compilers and tools won't understand this C++ variant—only dot-NET-enabled compilers can be used. The extensions to C++ that I've shown here are fairly minor, since C++ is a contemporary object-oriented programming language and has a large overlap with the object model of the CLR. Similar extensions were required to Eiffel to create "Eiffel#" (see the article "Eiffel on the Web: Integrating Eiffel Systems into the Microsoft .NET Framework" in the MSDN Library). Other languages, such as the non-object-oriented COBOL, will require more significant extensions in order for their compiled code to be accessible as first-class MSIL components.
XML Services
J2EE has a lot of catching up to do on the XML front. Dot-NET uses XML and provides XML services from top to bottom. ADO.NET uses XML for data transmission, MSXML provides general-purpose XML parsing and generation capabilities for applications, and Web services can be exported as SOAP interfaces. In addition, Microsoft released a beta of its XML for analysis specification in October 2000, which defines a SOAP-based protocol for remote method calls and data exchanges over HTTP. All in all, dot-NET is making it very easy for developers to take advantage of XML in innovative ways.
To its credit, Sun has been very active in the XML space, with the JAXP specification released in March 2000, then required in the J2EE 1.3 specification proposed final draft, which was released in October 2000. And in December 2000, Sun announced the Java API for XML Messaging (JAXM) and the Java API for XML Data Binding (JAXB). As of this writing, however, XML remains at the periphery of the J2EE framework (basic SAX and DOM APIs, and the use of XML for deployment descriptors). The parsers are there, and standard Java XML messaging and data binding APIs are in the works, but they need to be integrated seamlessly into the J2EE environment. XML messaging via JMS seems an obvious next step, exporting EJBs with XML messaging interfaces would also be very cool.
Strategic Practicalities
Where does all this leave us in terms of enterprise application frameworks? Well, at a high level, we're all in a much better place—Microsoft is fixing its Web application environment, making it a peer (at the very least) to J2EE, so when dot-NET arrives we'll have two very capable development frameworks to choose from. Perhaps more importantly, Microsoft has pushed the envelope in terms of application services (especially XML support), and the company's put a new twist on portability with the language-independent CLR. These efforts have spurred Sun's efforts around XML support (as mentioned previously with it's recently-announced JAXM and JAXB efforts), and in other areas as well. And both Sun and Microsoft, through all their bickering, have gotten the developers and technology strategists talking about new perspectives on enterprise development.
But at the end of the day, you have to decide which framework to adopt. Given all the technology issues I've discussed, the path to a decision is fairly simple. In any significant development effort, whether you're a software product vendor, a consultant on a custom development engagement, an internal developer of Application Service Provider (ASP) offerings, or building the internal enterprise infrastructure for an organization, you need to decide who your target customers are (today and in the future), what platforms you need to support from a strategic level (today and in the future), and what framework provider you want to partner with to push forward (today and in the future).
If you have the luxury of only supporting the Windows platform (with respect to both customers and internal development needs), and if you feel Microsoft is the right partner (for development tools, servers, administrative systems and so on) for your organization, then you have an easy decision indeed. Alternatively, if you have the luxury of defining a Web development practice based on a single language, and can't say with finality that Microsoft and Windows can be your sole vendor and platform, then you have an equally easy decision to make. Conversely, if you face the strategic need to support (or standardize on) Unix and other non-Windows servers, or if you want to leave your options open in other areas (tool vendors, application servers, component vendors and so on), then jump on the J2EE bandwagon. Alternatively, if supporting development in multiple languages is a strategic need of yours, and you can live with a single platform and tool vendor, then use dot-NET.
Of course, very few groups or organizations find themselves in any of these neat compartments, but hopefully these examples will serve as markers on this slalom course. Perhaps we will reach the day when enterprise systems crafted in either of these frameworks can be easily and seamlessly integrated with each other. Until then, choose your steed wisely and charge onward—there is an interesting road ahead either way.