Contact Free trial Login

About Classloading Isolation

To introduce the new classloading schema in Mule 4 we need to understand the classloading problems that we had in Mule 3.

  • Modules were not isolated from each other.

  • Modules were not isolated from the runtime.

  • Modules were not isolated from the applications.

This means that modules that used to use a some library could conflict with other modules, applications, or runtimes that used another version of the same library, causing classloading problems with errors like ClassNotFoundException or NoSuchMethodException, and confusing app developers, as well as producing runtime errors that were hard to troubleshoot.

In Mule 4, to tackle this problem, there is a brand new classloading schema that isolates the runtime, applications, and modules from each other. Each one of them specifies which packages (filtering is made by package and not made by class) will be export as part of their API, making those specific packages visible.

Modules Classloading Isolation

As said before, in Mule 4 classloading filtering is done by package and not by class, this means that the modules need to specify which packages is going to export for others to see. Lucky for you, the SDK makes all this work for you, but it’s pretty important that you, as a module developer, understands this rule.

It’s simple, the way that the SDK knows what packages to export is by introspecting each configuration, connection, and component return and input types, then getting their packages. This means that you never want to share a package between an exported class and another class that you don’t want to export.

The suggested package hierarchy (the one that we use for our modules) is:

\--> .api.*
\--> .internal.*

Where api.* contains all the classes that are going to be exported and seen by the application (Return Types, Subtypes, Input Types, Connection interfaces) and internal.* contains all the classes that contains the specific behavior of the module (Annotated classes, Connection and Operation implementations; and internal classes).

Knowing the Exported packages and return types.

It is recommended that all exported classes belongs to the module, it s highly discouraged and never recommended to return or receive objects from classes that belongs to a library that the module is using, because could end up in a library clash resulting in a module incompatible with other modules, app dependencies or even runtimes.

For example, think the next scenarios:

First Scenario: Exporting the same packages

We have two modules with two very different operations, both of them uses the same library for example GSON which parses a json and returns an instance of a JsonObject class. One of them uses GSON version 1.1 and the other one GSON version 2.3. The JsonObject class lives in the same package in both versions but the implementation is very different between each other, which made them incompatible.

The developers of the modules decided that it was a good idea to expose the JsonObject class as the return type of their operation:

First Module Operation
public JsonObject getEmployee(@Connection EmployeesServiceConnection connection, String id) {
  return new JsonObject(connection.getById(id));
Second Module Operation
public JsonObject getAllProducts(@Connection ProductsServiceConnection connection) {
  return new JsonObject(connection.getProdsJson());

But now when deploying a mule application, and the mule developer tries to use both modules that exports the same packages, mule will fail since you can’t have two different plugins exporting the same package.

Second Scenario: Returning two instances of the same Class in different versions.

Let’s grab the example above, but this time both operations won’t export the GSON packages, instead they return a java.lang.Object.

First Module Operation
public Object getEmployee() {
  // Here JsonObject is from GSON 1.1
  return new JsonObject();
Second Module Operation
public Object getAllProducts() {
  // Here JsonObject is from GSON 2.3
  return new JsonObject();

Now when the mule developer tries to use both modules and run the application, only one of the 2 versions will be loaded for the app, because both classes have exactly the same fully qualified name, so the fully qualified names will clash, resulting in different behavior in one of the modules (the one that did not get its verfion of the package loaded), or resulting in errors such as ClassCastException, NoSuchMethodException.

The @Export annotation

The @Export annotation can be used for extreme corner or complex cases when for some reason a module requires to export a class that is not automatically exported by the SDK.

The @Export annotation receives an array of classes to be exported.

Keep in mind that exporting a class will end up exporting all the classes in the same package as the exported class.

Was this article helpful?

💙 Thanks for your feedback!

Edit on GitHub