AsciidoctorJ 1.6.0 released, at last!

by Robert Panzer, Dan Allen and Sarah White -

After several years of brewing (or perhaps barrel aging?), we have finally released AsciidoctorJ 1.6.0! The artifacts are available from Bintray and Maven Central.

What is AsciidoctorJ?

AsciidoctorJ is the official library for running Asciidoctor on the JVM. With AsciidoctorJ, you can convert AsciiDoc content, analyze the structure of a parsed AsciiDoc document, and write Asciidoctor extensions in Java and other JVM languages.

Before you upgrade, there are important things you need to know, which this post covers. While AsciidoctorJ is still built on the strong foundation of the Asciidoctor RubyGem, it has matured into a project in its own right. This release brings stability and key enhancements to new and existing users alike. Grab a glass so we can pour you all the details.

A major stepping-stone to SemVer

Despite its version number, this release should be considered a major release. As such, it’s not binary compatible with the 1.5.x releases.

AsciidoctorJ 1.6.0 is a final, transitional release in preparation for the switch to Semantic Versioning (SemVer). We’re breaking the rules one last time, but for good reason. Given how long the 1.6.0 branch has been barrel aging, we wanted to allow it to be tapped to gather feedback and tease out unexpected surprises before making the jump to 2.0.0 and SemVer. If all goes well, we expect the 2.0.0 release to follow shortly after this one. At that point, we’ll begin versioning AsciidoctorJ according to the SemVer rules.

To learn more about SemVer and how it affects the Asciidoctor projects in general, see Making the Switch to Semantic Versioning.

Switching to SemVer also means that AsciidoctorJ will now be versioned independently of Asciidoctor. AsciidoctorJ has evolved into much more than just the Asciidoctor RubyGem repackaged for the JVM. It has a complete API, including Java-based extensions and tight integration with the Java ecosystem (e.g., JUL, Ruby object bridging, etc). Its own independent version line will allow it to evolve more quickly and help everyone better understand what to expect in each release. Most important, it will address the very situation that backed up the 1.6.0 release for several years.

New features

Major API and packaging changes

Since the start of AsciidoctorJ, we’ve learned so much about JRuby and how it bridges Ruby and Java objects. Using that knowledge, we’ve completely redesigned the low-level integration to address shortcomings in the original design.

That redesign required us to make breaking changes to the API, which were absolutely necessary for AsciidoctorJ to continue to move forward. But it also provided us with the opportunity to fix numerous bugs that could not be fixed in the 1.5.x line without disrupting compatibility. You’ll be happy to know that the redesign eliminates glitches that may have bitten you when writing extensions or navigating the document model using the 1.5.x releases.

As part of this change, we split the API and implementation classes into separate packages and JARs to properly isolate them. We also reconciled the competing document models by mapping all the node types in Asciidoctor and removing the ContentPart and StructuredDocument APIs. Refer to the API docs (api, impl) to see what types are available and how they are organized.

Reworked extension API

Thanks to the redesign, the JRuby-based implementation is much better hidden behind the API. In particular, there are no more references in the API to the RubyObject class from JRuby.

The interfaces for the Abstract Syntax Tree (AST) were completely renamed to better represent their purpose. For example, the AbstractNode interface was renamed to ContentNode, AbstractBlock was renamed to StructuralNode, and Inline was renamed to PhraseNode. Even more of the AST has been mapped in Java, including list and definition lists, as well as helpers for creating a table structure in an extension processor.

Overall, the APIs for extensions and the AST should feel like a pure Java library (e.g., getAttr() is now getAttribute()). You can even define extension processors using annotation-based configuration (e.g., @Name).

Check out the integrator’s guide to learn all about it and find lots of examples.

Added converter API

A new converter API has been introduced, making it possible to implement a converter for a custom output format entirely in Java. This API allows you to extend Asciidoctor from Java, similar to what you could already do using the extension APIs. You can learn how to use this new API by following the converter documentation.

In fact, there’s already one converter that’s using this API from Groovy to convert AsciiDoc to Leanpub-flavored Markdown.

Access to Asciidoctor’s logs

Asciidoctor logs error and warning messages to the Ruby logger whenever it encounters a problem in the document. When integrating Asciidoctor into your own software, it’s important to capture these log messages in order to handle them appropriately. Previously, these messages were out of reach of AsciidoctorJ. That’s no longer the case. The new Log Handling API picks up messages logged in Asciidoctor and routes them to your own log handler.

Better documentation

AsciidoctorJ 1.6.0 offers extended and tested documentation for writing extension and converters. This documentation is guaranteed to be correct because it’s tested with every commit ;) And the process is underway to incorporate the documentation into the new Antora-based documentation site for Asciidoctor.

Refer to the release notes for a more detailed account of what changed in this release. Keep in mind that, since the development of 1.6.0 ran in parallel with 1.5.x for so long, many changes from 1.6.0 may already be familiar to you as they were backported into the 1.5.x release line.

Migration notes

If you only use the Asciidoctor, Asciidoctor.Factory, and *Builder classes (e.g., Asciidoctor#convertFile), you likely don’t have to change your code. In fact, the Asciidoctor Maven Plugin and the Asciidoctor Gradle Plugin already work with this release, primarily because they only rely on the public load/convert APIs.

The necessary changes to the AST and extension APIs did introduce breaking changes that require modifying your existing extensions. Migrating an extension is a matter of conforming to the new API. This mostly involves switching to the new names for AST interfaces and aligning with the updated method signatures. Aside from the name and signature changes, the API should still be recognizable and thus compatible with your existing logic.

Here are several examples of extensions written for both AsciidoctorJ 1.5.8 and 1.6.0 to give you an idea of the types of changes to look out for:

If you have difficulty migrating your extension or document introspection code, please file an issues in the issue tracker so we can find a path forward.

Java compatibility

Asciidoctor 1.6.0 requires at least Java 8. It also runs on, and is tested against, Java 11.

Starting in AsciidoctorJ 1.6.0, the project will align its Java compatibility with the official Oracle Java SE support roadmap. While AsciidoctorJ doesn’t require Oracle Java SE, that roadmap serves as a useful signpost for which versions of Java are considered current.

Asciidoctor 1.6.0 does not yet support running on the module path, a key feature of the Java Platform Module System introduced in Java 9. We’d like to get there in an upcoming version. That will depend, however, on the progress AsciidoctorJ’s dependencies make towards this goal, most notably JRuby. Despite this limitation, you can still run Asciidoctor 1.6.0 on Java 11.

Acknowledgements

We’d like to take this opportunity to name several key individuals who came together to make this release what it is today.

  • Jérémie Bresson for initiating the split between the API and implementation packages and modernizing the API signatures.

  • Abel Romero for his help with the design of the JUL logging integration and for testing it in the Maven plugin first.

  • Frank Becker for overhauling and streamlining the Gradle build.

  • Guillaume Grossetie for loads of cleanups and improvements across the API and implementation and for stress testing the redesign by launching AsciidoctorG.

  • Charles Nutter, Thomas Enebo, and the JRuby team for creating and maintaining JRuby, on which AsciidoctorJ is based.

  • And finally, our “elder”, Alex Soto for starting the AsciidoctorJ project and showing us a vision of what’s possible.

Of course, there are many more people to thank. As we’ve said many times, this project would not be possible without the incredible work and collaboration by the many volunteers who work on it. So, thank you!

Outlook for 2.0.0

The breaking changes aren’t over just yet. Heading towards 2.0.0, we want to further split the API of AsciidoctorJ and its implementation. Our big goal is to support alternative implementations underneath, such as Asciidoctor.js, using the same public API.

To help us get to 2.0.0, we ask that you test 1.6.0 and let us know if you run into any problems or changes that prevent you from migrating to it. Now’s the chance to get it right before the 2.0.0 release. Please file any issues you find in the issue tracker.

Thank you for coming on this journey with us as we work to bring the very best of AsciiDoc to the JVM.