Citrin is also CTO and the architect of JNBridgePro — the company is a provider of interoperability tools to connect Java and .NET frameworks.
Bridging the Java to .NET interoperability divide
With an increasing number of today’s enterprises using a mixture of both Java and .NET technologies, interoperability between the two platforms has become an imperative.
The various business reasons behind the need for interoperability include:
- the reuse of existing skills,
- technologies and systems,
- the need to reduce project costs,
- and the requirement for faster time to market.
While the reasons for interoperability have remained the same, the landscape of approaches has endured some change. Indeed, of the class-level interoperability approaches, only one — bridging — has truly prevailed.
Approaches to Java-.NET class-level interoperability
Depending on the business reasons for interoperability and the requirements of the application, a company might choose either a service-oriented architecture (SOA) or class-level integration.
For the purposes of this article, we will focus on class-level integration.
Three basic approaches
Historically, there have been three basic approaches to Java-.NET class-level interoperability:
• Porting the platform: Port the entire .NET platform to Java or vice versa. In addition, compile the developed code to the alternate platform.
• Cross-compilation: Convert Java or .NET source or binaries to .NET or Java source or binaries
• Bridging: Run the .NET code on a .NET Common Language Runtime (CLR), and the Java code on a Java Virtual Machine (JVM) or a Java EE application server. Add a component to manage the communications between them.
Platform porting and cross-compilation certainly have some overlap. But cross-compilation involves only a subset of the code and will usually try to substitute the API calls of one platform to another. Platform porting implies porting all APIs of one platform to the other. Additionally, cross-compilation normally happens once with the result that the Java code is converted to a .NET language (or vice versa). After cross compilation, the initial code base is no longer used.
When evaluating each approach, the following criteria are commonly used:
• Performance: How much overhead is involved in inter-platform communication?
• Direction of interoperability: Does the approach support Java calling .NET, .NET calling Java, or both? Are callbacks supported?
• Binary compatibility: Can we use the approach to access Java binaries from .NET, or do we need source code?
• Type compatibility: Does the approach offer full implementation inheritance? Using the approach, are values converted to native data types on the respective platforms, where possible?
• Portability: Does the approach work only on Windows, or is it cross-platform?
• Conformance to standards: Using the approach, is the behavior of the Java code guaranteed to conform to Java standards?
• Ability to evolve: Will the approach break as either the .NET or Java platform evolves?
Both platform porting and cross-compiling — while still in existence — have fallen off the interoperability radar to a significant degree, mostly because they’ve failed to meet all or some of these evaluation requirements. Here, we take a deeper dive into each.
Porting the platform
One software vendor attempted at one time to reimplement the entire .NET platform in Java as a set of Java packages. This meant that the framework became available to be called by any Java code that imported the relevant package. While porting .NET to Java allowed Java to call the .NET APIs, in itself it didn’t allow Java classes and .NET classes to call each other. To allow Java to call .NET, cross-compilation must also be used.
Platform porting offers a number of benefits, including low inter-platform overhead. But its pitfalls far outweigh its benefits, contributing to its near-demise. The .NET framework is quite large, and porting the entire framework is a daunting task. There are a number of namespaces in the framework that are tightly tied to the underlying .NET runtime, and it is not clear that it would be possible to fully implement them in Java. Even if these types of namespaces were available, the Java code loses its portability because it depends on the native Windows code. As .NET is a very large platform with many thousands of classes to port, this opens a Pandora’s box of complexity. Platform porting quickly became a very unlikely method to succeed in achieving interoperability because of the sheer size and complexity of the task.
Cross-compilation can either compile Java source to MSIL (the Microsoft Intermediate Language that runs on the .NET CLR), thereby truly making Java a .NET language, or compile Java source to a .NET language such as C# or VB.NET.
If compiling Java source to MSIL, full inheritance between Java and other .NET languages is possible, and if done properly, the Java compiler can be fully integrated with other .NET development tools. Full inheritance is supported between Java and other .NET languages, and there is full interoperability in both directions, so that Java methods can call methods written in other .NET languages, and vice versa.
But with cross-compilation, there exist a number of shortcomings — not least of which is that the resulting code will only run on Windows, unlike Java source code compiled into Java byte codes, which is cross-platform. Also, any Java code that calls .NET framework APIs is no longer portable, since it relies on calls to methods other than Java methods or Java APIs. Additionally, in order to use a Java-to-MSIL compiler, the Java source code is needed, which means that the option is not available if the user only has Java binaries (for example, a compiled Java library that the user purchased from a third party).
The differences between the Java and C# object models lead to some problems when integrating Java and C# classes. For example, when implementing a Java interface with constant fields, such an interface must be legally compiled into MSIL and used by other MSIL-compiled Java classes, but any C# classes attempting to implement the interface would not see the constants.
When compiling Java source to a .NET language, there is no overhead for inter-platform communication. Full inheritance is possible. In the case of binary cross-compilation, the approach works only when Java binaries are available. When the MSIL is cross-compiled to Java byte codes, the result is cross-platform.
There are some disadvantages to this cross-compilation approach as well. It is necessary either to re-implement the APIs for one platform in the other (that is, re-implement the Java API in .NET or the .NET framework in Java), or to translate one platform’s API calls to the equivalent on the other platform. Java byte codes translated to MSIL would only run on Windows. Finally, there is no guarantee that the behavior of Java code translated to MSIL will conform to Java standards.
Bridging solutions address the conversion issue by avoiding it. .NET classes run on a CLR, Java classes run on a JVM and bridging solutions transparently manages the communications between them. To expose classes from one platform to classes on the other, proxy classes are automatically created that offer access to the underlying real class. Thus, to allow calls from .NET methods to Java methods, proxies are created on the .NET platform that mimic the interfaces of the corresponding Java classes. A .NET class can inherit from a Java class by inheriting from the Java class’s proxy, and vice versa.
Bridging has a number of advantages over other interoperability approaches. For example, bridging can evolve as the platforms evolve. Future versions of Java and .NET will work with a bridging solution as long as they remain backward-compatible. As new versions of Java and .NET are introduced, they can be incorporated without having to update the bridging solution.
Bridging has the additional advantage that, since the Java runs on a JVM or a Java EE application server, it is not necessary to have source code; the solution will work when only Java binary is available. Finally, since the Java classes are still compiled to Java byte codes, they remain cross-platform.
Bridging solutions also often conform to standards. Since the actual runtime environment is a CLR or a JVM, and as long as the runtime environments and compilers conform to standards, the resulting code will exhibit conformant behavior.
In addition to these general advantages, bridging solutions support callbacks, allowing Java code to implicitly call .NET code without having to alter the Java code, both pass-by-reference and pass-by-value, automatic mapping of collection objects between native Java and native .NET, and on-the-fly generation of proxies for dynamically generated Java classes.
Rationale and reasoning
There are a number of reasons why one would wish to interoperate Java and .NET code, most of which centre around preserving an investment in Java code or Java developers, and using existing Java code in a new .NET setting.
Each of the various approaches to interoperability, platform porting, Java compilation to MSIL, cross-compilation, and bridging, offers advantages and is appropriate in different situations.
However, as the previous discussion shows, bridging solutions provide the best combination of portability, ability to evolve, conformance to standards and smooth interoperability. These advantages have ensured bridging’s survival as the interoperability solution of choice now and into the future.