Interacting with Java Libraries in Lucee 6.2

With Lucee 6.2, one of our main objectives was to simplify Java integration as much as possible. We focused on making it easier to load libraries and adapt existing Java code to work seamlessly in Lucee. To achieve this, we approached it with a simple challenge: pick a third-party Java library and try to use it in Lucee with the least amount of friction. If we encountered any hurdles, we adapted Lucee accordingly. In short, we learned a lot from real-world examples and worked to overcome any unexpected challenges.

As a result of this process, two new extensions will be coming out:

  • Quartz Scheduler
  • LangChain4j for Retrieval-Augmented Generation (RAG)

But let’s dive into an example instead of just talking about it.

Example: ZXing (QR Code Generator)

For this example, we followed this post as a guide to get ZXing working with Lucee. Here’s how the Java code looks in Lucee:

component javasettings='{
        "maven":[
            {
                "groupId" : "com.google.zxing",
                "artifactId" : "core",
                "version" : "3.3.0"
            },
            {
                "groupId" : "com.google.zxing",
                "artifactId" : "javase",
                "version" : "3.3.0"
            }
        ]
    }' {
        import java.io.File;
        import java.util.HashMap;
        import com.google.zxing.BarcodeFormat;
        import com.google.zxing.EncodeHintType;
        import com.google.zxing.MultiFormatWriter;
        import com.google.zxing.NotFoundException;
        import com.google.zxing.client.j2se.MatrixToImageWriter;
        import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

        public static void function createQR(String data, String path, numeric height, numeric width) {
            
            var hashMap = new HashMap();
            hashMap.put(EncodeHintType::ERROR_CORRECTION,ErrorCorrectionLevel::L);
            
            var matrix = new MultiFormatWriter().encode(data, BarcodeFormat::QR_CODE, width, height);
            MatrixToImageWriter::writeToFile(
                matrix,
                listLast(path, "."),
                new File(path)
            );
        }
}

Using this component in Lucee is straightforward:

<cfscript>
    path = getDirectoryFromPath(getCurrentTemplatePath()) & "qr.png";
    ZXingSimple::createQR("www.lucee.org", path, 200, 200);
    echo('<img src="#contractPath(path)#">');
</cfscript>

As you can see, we were able to take the Java example almost as-is, with only a few small modifications.

Maven Integration

First, we import the necessary Maven dependencies. Lucee will automatically download the libraries and all their dependencies when needed.
Maven is similar to ForgeBox, but operates at a much larger scale since it is used for Java packages globally.

component javasettings='{
        "maven":[
            {
                "groupId" : "com.google.zxing",
                "artifactId" : "core",
                "version" : "3.3.0"
            },
            {
                "groupId" : "com.google.zxing",
                "artifactId" : "javase",
                "version" : "3.3.0"
            }
        ]
    }' {
    ...

More details about Maven support in Lucee 6.2: Maven Documentation

Importing Java Classes

Next, we import the necessary Java classes. In Lucee 6.2, you can import Java classes the same way you import CFML components. You can even use wildcards like * to import all classes from a package.

...
import java.io.File;
import java.util.HashMap;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
...

More details about import support in Lucee 6.2: Import Documentation

Interacting with Java Classes

Finally, interacting with the Java classes themselves is simple. There’s no need for createObject or other complex syntax. You can now load Java classes just like CFML components, using the new operator and :: for static methods.

...
public static void function createQR(String data, String path, numeric height, numeric width) {
            
    var hashMap = new HashMap();
    hashMap.put(EncodeHintType::ERROR_CORRECTION, ErrorCorrectionLevel::L);
    
    var matrix = new MultiFormatWriter().encode(data, BarcodeFormat::QR_CODE, width, height);
    MatrixToImageWriter::writeToFile(
        matrix,
        listLast(path, "."),
        new File(path)
    );
}
...

More details about the new operator in Lucee 6.2: New Operator Documentation

Bonus: Enable Documentation in Lucee 6.2

If you’ve enabled the ā€œdocumentationā€ monitor, all these recipes linked above and more will appear at the end of the page in the monitor output.

8 Likes

the Maven part is super interesting!

When I use third party java libraries I always waste a lot of time copying them to my working directory.
Now everything should be easier.

Thanks! :heart:

I know and updating them is even worse. Specially to get sure you have all dependencies in the correct version. Using it that way (in the component) you also get a better separation, so no conflicts when you load the component in an environment using different versions of the libraries.

3 Likes

Oh boy… cant wait to play with this… #superawesome

Get donating people! #morefeatures #moredevtime

1 Like

Our server is offline,firewall setting only connect to server web service (443 port). If I download the jar file from ā€˜https://repo1.maven.org/maven2/com/google/zxing/core/3.5.3/core-3.5.3.jar,’ how do I manually copy it to the correct directory in Lucee 6.2?

Downloaded jars are stored under lucee-server\mvn

I’d suggest doing it locally in dev to get the correct pattern and to use that as a template for your prod deployment

We plan to download these as part of the warmup process

https://luceeserver.atlassian.net/browse/LDEV-5281

We are just getting around to Lucee 6.

Thank you thank you thank you thank you!

I spend almost all of my time integrating with third-party systems. We already use maven to populate the libs folder and then either write things in java and bundle them into a jar or end up using Java in Lucee with all the unnecessary boilerplate.

Before that it was using Java loader and having it autoload entire firectori a if jars that we downloaded.

This is beautiful and simple and I’m so happy about it. Thank you!

I feel like so much of java functionality these days comes from annotations (or packages as annotations). Using the exact same annotations in a component would be ideal instead of you all having to add them into the language as attributes. I think this probably enables that although not as natively as would be preferred. I would LOVE to use @controller and @service etc. I guess we can do that now? this is amazing. I have not played with it but I will soon. Thanks. I can’t wait to build with this!

1 Like

I’m trying to code using function type=java,
https://github.com/lucee/lucee-docs/blob/master/docs/recipes/java-in-functions-and-closures.md
I don’t know how to call Lucee functions and other function type=java functions from within a function type=java.

I hope there can be more examples of commonly used Lucee data types and operations in function type=java, such as working with arrays, structs, queries, and using anonymous functions as callbacks.
For example, here’s a small sample of common struct operations:

// lucee
<cfscript>
function getData(id){
  var result = {};
  ...process something...
  if(result.keyExists("error")){
      result['message'] = 'fail';
  }else{
      result['message'] = 'success';
  }
  return result;
}

</cfscript>
// type=java
struct function getData(int id) type="java" {
  lucee.runtime.type.StructImpl result = new lucee.runtime.type.StructImpl();
  if(result.containsKey("error")){
      result.set("message", "fail");
  }else{
      result.set("message", "success");
  }
  return result;
}