Blog tagged interop

Write in Ceylon, deploy as OSGI, use in Java EE

... or how to use Ceylon inside Java EE application servers.

The Ceylon language is inherently modular, and is shipped with a complete infrastructure that allows leveraging this modularity out-of-the box. However Ceylon is not captive of its own infrastructure. After the Java and JS interoperability efforts, the 1.1.0 version has brought out-of-the-box compatibility with OSGI, which enables running Ceylon code inside many other containers.

Every module archive produced by the Ceylon compiler contains OSGI headers in its MANIFEST file, that describe the module as it should seen by OSGI containers.

Containers tested so far are:

  • Apache Felix 4.4.1,
  • Oracle Glassfish v4.1,
  • Equinox platform,
  • JBoss WildFly 8.0.0.alpha3 (with JBossOSGi installed)

Of course, the Ceylon distribution and SDK modules should first be added inside the OSGI container as OSGI bundles.

But instead of writing long explanations here, let me direct you to some concrete examples provided, with the required instructions, in the following repository:

For the moment, it contains a single example that, though very simple, will give you the main steps to start.

It also shows the use of a Ceylon module totally outside Ceylon's standard infrastructure, even outside the JBoss world, in a Web application servlet running on a Glassfish v4.1 application server. But of course you should be able to run it inside other OSGI-enabled application servers or containers.

In the next examples we'll try to go further an do more interesting things such as providing services, using Ceylon annotations (which are compatible with Java annotations), or using OSGI services.

Please report any problem you might encounter while testing, and feel free to submit pull requests for any other successful use cases you might have built.

Looking forward for your remarks, and for the time to write the following examples.

Null and Java interop

The way Ceylon handles null is one of the big attractions of the language. Ceylon features a typed null value. The object null is just an instance of the perfectly ordinary class Null from the point of view of the type system. But when the compiler backend transforms Ceylon code to Java bytecode or JavaScript, the class Null is eliminated and the value null is represented as a native Java or JavaScript null at the virtual machine level.

This is great for interoperation with native Java or JavaScript. However, when designing our Java interop, we had to take into account that Java's null isn't typed, and that the information about whether a Java method might return null, or whether a parameter of a Java method accepts null is "missing" and not available to the Ceylon typechecker. I think we've done the right thing here, but the approach we took has surprised a couple of people, so I guess it's well worth calling explicit attention to what we've done and why.

Consider the following useful method of java.lang.String:

 public String toUpperCase(Locale locale) { ... }

By checking the implementation of this method, I determined that:

  1. toUpperCase() never returns null, and that
  2. the argument to locale must not be null.

Unfortunately, this information isn't available to the Ceylon typechecker. So perhaps you might expect that Ceylon would take a very heavyhanded route, treating the return type of toUpperCase() as String?, and forcing me to explicitly narrow to String using an assertion:

assert (exists uppercased = javaString.toUpperCase(locale));

In fact, this is not what Ceylon does. This approach would make interoperation with Java a nightmare, resulting in code full of hundreds of useless assertions. You would need an assertion essentially every time you call a native method.

Worse, the "heavyhanded" approach would prevent me from passing the value null as an argument to a parameter of a Java method. What would it mean to "assert" that a method parameter accepts null? That's not an assertion that can be tested at runtime!

Nor would the "heavyhanded" approach in practice even protect me from NullPointerExceptions. As soon as I call native Java code, a NullPointerException can occur inside the Java code I call, and there's nothing Ceylon can do to protect me from that.

So instead, Ceylon takes a "lighthanded" approach at the boundary between Java and Ceylon, letting you write this:

String uppercased = javaString.toUpperCase(locale);

The typechecker knows this is a Java method, and that the information about null is missing, so it assumes that you know what you're doing and that toUpperCase() never returns null.

But what if you've got it wrong, and toUpperCase() does return null at runtime? We would not want that null value to propagate into Ceylon values declared to contain a not-null type!

What the Ceylon compiler does is insert a null check on the return value of toUpperCase(), and throws a NullPointerException to indicate the problem. If you ever see that NullPointerException, at runtime, you're supposed to change your code to this:

String? uppercased = javaString.toUpperCase(locale);

This is essentially the best Ceylon can do given the information available to it. The NPE is not a bug! It's not even really a "gotcha".

Still, the NullPointerException might look like a bug to novice user. (Indeed, Stef recently noticed someone describe it as a bug in a conference presentation.) So I'm asking myself how we can make this less surprising. There's a range of options, including:

  • add a better error message to the NullPointerException,
  • throw an AssertionException instead of a NullPointerException, or even, perhaps,
  • following the approach we use for JavaScript interop, require you to surround "lighthanded" code in a special construct that suppresses the "heavyhanded" typechecks, dynamic Null { ... } or something.

I lean towards the first of these options, I suppose.

Whatever we do, we need to document this better. Right now, the documentation is kinda hidden.