A few months ago, I wrote up a blog post exploring Java integration in BoxLang and in that post, Robert Zehnder suggested I get Flexmark working as well. Flexmark is a Java Markdown library, and turns out, I blogged about it last September: "Parsing Markdown in ColdFusion"

That post demonstrated how to use Flexmark in ColdFusion, and initially I didn't think there would be a point in demonstrating the same thing in BoxLang as the syntax would be near identical, but turns out, there's one small difference that's absolutely worth sharing.

Ok, so first off, I grabbed flexmark-all-0.64.8-lib.jar from Flexmark's Maven repository. In the previous post I made use of ColdFusion's application framework to define my Java load paths so that ColdFusion could find the jar. For BoxLang, I could do that as well but as I'm building a command line tool and not a web app, I instead made use of the BoxLang feature where createObject lets you pass in a jar file. Keep that in mind if you make use of this code in a web environment, you could simply a bit in that context.

Here's the initial function I wrote which is a copy of the previous one, with the addition of specifying the jar:

function markdownToHTML(str) {

	ds = createObject("java", "com.vladsch.flexmark.util.data.MutableDataSet","flexmark-all-0.64.8-lib.jar");
	ps = createObject("java", "com.vladsch.flexmark.parser.Parser","flexmark-all-0.64.8-lib.jar").builder(ds).build();
	hm = createObject("java", "com.vladsch.flexmark.html.HtmlRenderer","flexmark-all-0.64.8-lib.jar").builder(ds).build();
	
	doc = ps.parse(str);
	return hm.render(doc);

}

As I mentioned in the previous post, this code came from reading the sample Java code for Flexmark. But this didn't work! Why?

When run, I got this stacktrace:

ortus.boxlang.runtime.types.exceptions.NoMethodException: No such 
method [builder] found in the class [com.vladsch.flexmark.parser.Parser] 
using [1] arguments of types [[class java.lang.Class]]

This is referring to the second line in the function and seems to imply the ds variable is an instance of java.lang.Class, not the class I had initially created.

Turns out, this is expected as the initial call of creating the object just loads the class, but doesn't actually make an instance of it. The fix is ridiculously simple (scroll to the right):

function markdownToHTML(str) {

	// .init() is important!
	ds = createObject("java", "com.vladsch.flexmark.util.data.MutableDataSet","flexmark-all-0.64.8-lib.jar").init();
	ps = createObject("java", "com.vladsch.flexmark.parser.Parser","flexmark-all-0.64.8-lib.jar").builder(ds).build();
	hm = createObject("java", "com.vladsch.flexmark.html.HtmlRenderer","flexmark-all-0.64.8-lib.jar").builder(ds).build();
	
	doc = ps.parse(str);
	return hm.render(doc);

}

I literally just added an init call for the ds object to properly load an instance and get it running. Something to keep in mind, and given the a) huge amount of open source Java out there and b) BoxLang runs on the JVM, it's an important tip to keep in mind.

Before I leave, one more quick note. In my test script, I made use of another BoxLang feature, tag islands. Tag islands are a way to incorporate tag based syntax within scripts, and honestly, the whole idea horrifies me. I don't know why, it just does. That being said, I've not actually used the feature before, and it can be really useful, for example:

In the script above, the tag island is how I wrote ad hoc text within a script context inside the bx:savecontent variable. In this case, I think it worked great. I started off with a multiline string instead, but since variables inside strings are interpolated, and since the # symbol is used for that, I had to double up my Markdown headers which made the code less readable. Although usually I'd imagine the input I'd be using would come from a database or some other source. GenAI APIs often return Markdown so this would be useful in that case for sure.

If you want to try this yourself, install BoxLang real quick and find the demos here: https://github.com/ortus-boxlang/bx-demos/tree/master/java-interop

p.s. In case you were curious, I switched to an embedded Gist for the last code sample as I couldn't figure out how to nicely escape the Markdown within the source. I'm sure I could (I tried backslash, it only half worked) but the Gist was easier.