Truffle Unchained — Portable Language Runtimes as Java Libraries

Christian Humer
graalvm
Published in
7 min readSep 19, 2023

--

For the past couple of years, Truffle languages were installed using our built-in GraalVM Updater tool called gu. To install a language, you downloaded Oracle GraalVM and then ran the gu install command to download and unpack components. Together with the GraalVM for JDK 21 and Truffle/Polyglot 23.1 releases, we remove gu and provide languages as Maven dependencies instead. In this blog post, we want to dive into why this change was necessary, what we gain from it, and what you need to do to benefit.

What was wrong?

Arguably, the old model of installing a language directly into the JDK had merits. For example, you didn’t need to generate a class or module path for the language dependencies. After installation, a Java application or the Java compiler automatically picks up language dependencies. In addition, we could ship language launchers that allow you to launch any language conveniently from the JDK. Over the years, we learned from community feedback that there were some fundamental problems with this approach:

  • Installed applications and runtimes are supposed to stay immutable. GraalVM Updater modified the installation, making it hard to work with systems and package managers requiring immutability.
  • Upgrading a language required to upgrade and modify the JDK. This made any language upgrade project a JDK upgrade project. In practice, this could become even more difficult when separate teams perform those upgrades.
  • The additional language launchers embedded in the Java Runtime are alien to the Java community and, at the same time, alien to other language ecosystems. In other words, many don’t feel comfortable with a Python launcher being in the JDK bin directory.
  • Due to a lack of existing tooling, using an additional package manager like GraalVM Updater was causing additional integration complexity. For example, there is no way for a Java application to express a dependency on a language runtime like JavaScript.

What did we change?

After some work-intensive months, we are finally ready to “unchain” Truffle from the JDK. Starting with GraalVM for JDK 21, we are making the following changes:

  • All polyglot language runtimes we develop can now be used as Java libraries from Maven Central.
  • Future Polyglot versions will be backward compatible with the latest Oracle GraalVM long-term supported release. We are starting this with GraalVM for JDK 21.
  • We removed GraalVM Updater from the distribution without replacement.
  • We are shipping more standalone distributions for languages without such a distribution before, like Node and LLVM. We also provide dedicated builds with a preinstalled GraalVM JDK called JVM-standalone for every language.
  • We require using GraalVM for JDK 21 if you use an optimizing runtime.

How to migrate?

To make migration as simple as possible, we grouped all artifacts in the org.graalvm.polyglot group for easy access. This way, all you need to know is a language component identifier. All component identifiers used with gu can now be used as Maven artifact IDs. For example, the Maven configuration to embed JavaScript looks like this:

<dependency> 
<groupId>org.graalvm.polyglot</groupId>
<artifactId>polyglot</artifactId>
<version>23.1.0</version>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<!-- Select language: js, ruby, python, java, llvm, wasm, languages -->
<artifactId>js</artifactId>
<version>23.1.0</version>
<type>pom</type>
</dependency>

With Gradle, the configuration is even shorter:

dependencies {
implementation("org.graalvm.polyglot:polyglot:23.1.0")
// Select language: js, ruby, python, java, llvm, wasm, languages
implementation("org.graalvm.polyglot:js:23.1.0")
}

All dependencies are licensed using GraalVM Free Terms and Conditions (GFTC) by default. Add the -community suffix to use community versions that use only Open Source licenses. For example, js-commmunity refers to JavaScript without GFTC-licensed extensions.

Note: All polyglot APIs remain fully compatible with the previous versions. You don’t need to change your Java code with this release.

We also split the GraalVM SDK module into more fine-grained modules. For a polyglot embedding, you should now use the org.graalvm.polyglot:polyglot dependency. When using the Java module system, you can replace org.graalvm.sdk with org.graalvm.polyglot as the required Java module. We still deploy the deprecated GraalVM SDK module on Maven Central, so you don’t need to migrate immediately.

More information on using the new dependencies can be found in the reference documentation. We also created a new polyglot embedding demo repository with sample Maven and Gradle configurations to give you an easy starting point.

Since GraalVM Updater was removed, you can remove all usages with this release. If you were using one of the language launchers, please use the standalone downloads provided as part of the GraalVM for JDK 21 release instead.

What's better?

We think that these changes will be worth it for the following reasons:

  • Ecosystem Compatibility: No matter which ecosystem our users come from, they should feel right at home. Java developers can now use languages as Maven dependencies like they use Java libraries daily. Language launcher users get improved standalone distributions that look and feel like the reference implementation.
  • Platform Independent JARs: Java developers expect libraries to be platform-independent by default. All JAR files we deploy contain resources and native libraries for all platforms we support. This allows you to move applications with their dependencies across platforms seamlessly.
  • Improved Immutability: Oracle GraalVM and GraalVM CE are now immutable. Languages can be updated without altering the JDK installation using Maven dependencies. This helps with platforms and package managers that require immutability.
  • Simplified Language Upgrades: Upgrading a language runtime as a Java developer becomes as simple as changing the version number of the Maven dependency. Language upgrades can now be performed separately from JDK upgrades.
  • Backward Compatibility: The provided backward compatibility to the latest long-term supported released JDK allows language embeddings to continue using older JDKs while benefiting from new polyglot language features.
  • Standardized Configuration: Polyglot runtimes are now added using the module or class path; this works with all JDKs, including the Native Image driver, the same way. This simplifies integration with systems running on multiple JDKs.

What's worse?

There’s no sugarcoating it: changing how languages are used is a breaking change, and migration is likely necessary if you use GraalVM Updater as part of your setup script. In addition, there are two known issues with this release that we intend to fix in coming releases.

  • Polyglot programming: As mentioned in a previous section, if you were using one of the standalone language launchers in the JDK, you now need to use standalone language downloads instead. The standalone distributions have limited support for adding or installing additional languages. Python and Ruby already readded support for this feature to their JVM standalone distributions. Other languages will follow in the subsequent releases. You can also continue to use multiple languages with the Polyglot Embedding API.
  • Limited Polyglot Isolate Support: With the GraalVM for JDK 21 release, we only deploy JavaScript as a polyglot isolate image to Maven Central. We will ship more languages in upcoming releases. If you are interested in using languages as isolates sooner, please get in touch with us.

Why now? Why not earlier?

This step might seem obvious to some, but it wasn’t in the beginning when we first released GraalVM in 2019. It makes sense to outline why we didn’t do this earlier / from the beginning. This section goes into a lot of detail, but it is necessary to elaborate on some background to explain our decisions.

  • Strong coupling with the compiler: The Truffle optimizing runtime and the Graal compiler were historically developed together for a long time. In fact, the Graal compiler module even contained the Truffle optimizing runtime. When two components live in the same module for a long time, they get stronger and stronger coupling over time. A big part of the work necessary for Truffle Unchained was decoupling the compiler more from the Truffle runtime.
  • Variability: Decoupling two components and allowing their independent use adds additional variability. In the past, the Truffle optimizing runtime and the Graal compiler were guaranteed to have the same version. We had to introduce a new API between these two components to allow this additional variability. Interfaces like this make evolving the platform more complex, but hopefully, our users won’t have to worry about it.
  • Encapsulation: We want to ensure that our embedding API artifacts sufficiently hide implementation details. We do this because we need to balance API stability for embedders and simultaneously allow for the independent evolution of our language implementations. When GraalVM was first released in 2019, we shipped with Java 8 support, so module encapsulation was unavailable. Therefore, we had to resort to class loader isolation for encapsulation instead, but class loader isolation only worked if we could load languages from a known location. Since then, we dropped support for Java 8, so we can rely on Java module encapsulation, which can also be used when the language dependencies are provided on the class or module path.
  • Resource files: Many languages require platform-specific files and native libraries to run. Many of these files need to be physically stored on disk for compatibility. We previously installed those resources when GraalVM Updater installed a language. With Truffle Unchained, we developed a new Internal Resources API that can be used by language implementations that require local files on disk. This API allows the expansion of files and native libraries from a JAR file to a versioned local cache folder when the language is used for the first time on a new platform. The complexity of such a feature prevented us from doing this in the past, but ultimately, we had to bite this bullet.
  • Enterprise extension support: Similar to separating the Graal compiler from the Truffle runtime, we also had to separate our enterprise extensions that can only work as part of Oracle GraalVM. We updated our enterprise extensions to activate only if used with Oracle GraalVM.
    Additionally, the recent change to the GFTC license allows us to deploy enterprise extensions to Maven Central.

Tell us what you think!

Please tell us your thoughts on our community channels. Do you like the new model, or do you prefer the old model? How can we help you migrate? Please don’t hesitate to report any issues you might encounter on GitHub.

Further Reading

--

--