Feedback on the new java integration, function() type="java"

I’ve been experimenting with the new Java integration in Lucee 6.2.1 and appreciate the direction it’s heading. However, I’ve encountered several issues that have made it challenging to continue development using this feature:

  1. Error Messages Lack CFML Line Numbers
    When exceptions occur within type="java" functions, the error messages don’t indicate the specific line number in the .cfm file where the error happened. This omission makes debugging difficult, as I often have to guess the source of the problem.
  2. Use of var Keyword Causes Errors
    Attempting to use var for local variable declarations within type="java" functions results in the following error:
    Local variable type inference NYI; Failed in C:\lucee\tomcat\webapps\ROOT\java_test.cfm:3
    (java 11 support var Local Variable Type Inference)
  3. Type Casting Parameters Fails
    Passing parameters of certain types to Java functions leads to casting errors. This issue was also discussed in this thread.
  4. Calling Other type="java" Functions
    It’s unclear how to invoke other type="java" functions or standard Lucee functions from within a type="java" function.
  5. Declaring Custom Java Classes
    Unlike Adobe ColdFusion’s <cfjava> tag, Lucee doesn’t seem to offer a straightforward way to declare and use custom Java classes directly. For reference, here’s the Adobe documentation on <cfjava>.

Thank you again for the ongoing work on Lucee. I’m looking forward to seeing how the Java integration continues to evolve, and I’d really appreciate any advice or clarification on the points above.

thanks for the feedback.

When using type="java" if you aren’t running a jdk, Lucee falls back on the jamino compiler, which has some limitations. (docs updated to reflect that)

With a JDK, the error I’m getting back does have the correct context (yes it’s 7, but the 6 throws the same exception)

We have extended the Java support in Lucee 7 further, the upcoming 6.2.2 RC already has solved some issues, with a few more outstanding.

https://luceeserver.atlassian.net/jira/software/c/projects/LDEV/boards/53?label=java&sprints=110&useStoredSettings=true

@micstriit is going to add some move examples for this integration

2 Likes

I’d like to highlight an issue I’ve encountered with type="java" functions: when runtime errors occur, the error messages often lack specific line numbers in the .cfm file, making debugging challenging.

For example:

<cfscript>
    java.math.BigDecimal function getBigNumberAdd(string A, string B) type="java" {
        java.math.BigDecimal a = new java.math.BigDecimal(A);
        java.math.BigDecimal b = new java.math.BigDecimal(B);
        return a.add(b);
    }
    echo(getBigNumberAdd("", ""));
</cfscript>
<cfscript>
    all_travelers=queryExecute("select * from travelers",{},{datasource="lucee"});
    japen=queryExecute("select * from all_travelers where county='japen'",{},{dbtype="query"});
    korea=queryExecute("select * from all_travelers where county='korea'",{},{dbtype="query"});
    echo(getBigNumberAdd(japen.count,korea.count));
</cfscript>


java_test_cfm$cf$getBigNumberAddb: line 25 but code less then 25 line

If A or B is empty strings, the BigDecimal constructor throws a NumberFormatException. However, the error message doesn’t indicate the exact line in the .cfm file where the error occurred… Even if it doesn’t indicate the exact line, having a reference point a few lines before or after the problematic code would greatly aid in debugging.

Is there a way to enhance error reporting in such scenarios, perhaps by ensuring a JDK is present or through other configurations? Improved error messages with precise line numbers would greatly aid in debugging.

Thank you for your continued efforts in improving Lucee.

1 Like

TBH the experience with type=“java” will never be quite as nice as with native cfml as we have more control over the bytecode for lucee and java is way stricter than cfml

<cfscript>
    echo(server.lucee.version &"<br>");
    echo("java: " & server.java.version &"<br>");
    dump(server.java);

    b = new component {
        import java.math.BigDecimal;
        function getBigNumberAdd(string A, string B) {
            var a = new BigDecimal(A);
            var b = new BigDecimal(B);
            return a.add(b);
        }
    };
    echo(b.getBigNumberAdd(2, 3)); 
    flush;
    echo(b.getBigNumberAdd("", "")); 

</cfscript>

there’s a different bug relating to inline components coming into play here

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

moving that inline component into a cfc, however, produces

1 Like

I understand that the experience with type="java" functions may not be as seamless as with native CFML due to inherent differences. However, I wanted to highlight that when using Java directly, error messages typically include precise line numbers when runtime errors, which greatly aids in debugging, especially in larger codebases.


java

BigDecimalCalculator.java

import java.math.BigDecimal;

public class BigDecimalCalculator {
    public static BigDecimal add(String A, String B) {

        BigDecimal a = new BigDecimal(A);
        BigDecimal b = new BigDecimal(B);
        return a.add(b);
    }
}

javac BigDecimalCalculator.java -encoding utf8
jar cf BigDecimalCalculator.jar BigDecimalCalculator.class

<cfscript>
    echo(server.lucee.version &"<br>");
    echo("java: " & server.java.version &"<br>");
    dump(server.java);
    dump(createObject("java","System").getenv("JAVA_HOME"));
    dump(createObject("java","BigDecimalCalculator",["C:\lucee\lib\BigDecimalCalculator.jar"]).add("2",""));
</cfscript>

lucee

<cfscript>
    echo(server.lucee.version &"<br>");
    echo("java: " & server.java.version &"<br>");
    dump(server.java);
    dump(createObject("java","System").getenv("JAVA_HOME"));
    object function getBigNumberAdd(string A, string B) type="java"{

        java.math.BigDecimal a = new java.math.BigDecimal(A);
        java.math.BigDecimal b = new java.math.BigDecimal(B);
        return a.add(b);
    }
    echo(getBigNumberAdd("2",""));
</cfscript>

I get a slightly different error with the error template, rather than a dump?

btw, newer versions of java like 21 produce more informative stack traces (like NPEs include more information) (tho currently, lucee is using the janino compiler over the jdk one, but this is a runtime exception, so it’s java 21)

the caused by has a little more detail, but the original context is indeed being lost

this is what the compiled java looks like, line 26 is the start of the try block

// Source code is decompiled from a .class file using FernFlower decompiler.
import java.math.BigDecimal;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.runtime.JF;
import lucee.runtime.PageContext;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
import lucee.runtime.type.FunctionArgument;
import lucee.runtime.type.FunctionArgumentImpl;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.UDF;

public final class javafunc2_cfm$cf$getBigNumberAdd1 extends JF implements UDF {
   public Object invoke(String var1, String var2) throws Exception {
      CFMLEngine var3 = CFMLEngineFactory.getInstance();
      PageContext var4 = var3.getThreadPageContext();
      BigDecimal var5 = new BigDecimal(var1);
      BigDecimal var6 = new BigDecimal(var2);
      return var5.add(var6);
   }

   public Object call(PageContext var1, Object[] var2, boolean var3) throws PageException {
      CFMLEngine var4 = CFMLEngineFactory.getInstance();
      if (var2.length == 2) {
         try {
            return this.invoke(var4.getCastUtil().toString(var2[0]), var4.getCastUtil().toString(var2[1]));
         } catch (Exception var6) {
            throw Caster.toPageException(var6);
         }
      } else {
         throw var4.getExceptionUtil().createApplicationException(String.valueOf("invalid argument count (").concat(String.valueOf(var2.length)).concat(String.valueOf("), java function [invoke(java.lang.String, java.lang.String)] takes 2 arguments")));
      }
   }

   public FunctionArgument[] getFunctionArguments() {
      return new FunctionArgument[]{new FunctionArgumentImpl(KeyImpl.initKeys("A"), "java.lang.String", (short)7, false, 0, true, "", ""), new FunctionArgumentImpl(KeyImpl.initKeys("B"), "java.lang.String", (short)7, false, 0, true, "", "")};
   }

   public javafunc2_cfm$cf$getBigNumberAdd1() {
      super("getBigNumberAdd", 1, 0, 0, "Object", "", (Boolean)null, (Boolean)null, "", "", 0, (Boolean)null, (Boolean)null, 1);
   }
}

1 Like

Thank you for sharing the decompiled Java class—it was very insightful.

It appears that all runtime errors point to line 26, which corresponds to the try block. This makes it challenging to identify the exact location of errors within the original CFML code, especially in larger codebases.

It would be immensely helpful if Lucee could enhance runtime error reporting to include the corresponding Java code and its line numbers. This addition would greatly aid in debugging complex applications.

Additionally, I have a question: How can I invoke a standard CFML function or another type="java" function from within a type="java" function? I’m looking for guidance or examples on how to achieve this.

Thank you again for your assistance and for the continuous improvements to Lucee.

1 Like

@micstriit does have it on his long TODO list to dive and address your questions about working with type=“java”.

I’m not sure how good your java skills are, you could always try diving in and improving the exception handling yourself. I’d start by setting some breakpoints in the error handling code you see in the stacktraces.

If you checkout the 7 branch into vscode with java extensions installed, it’s quite easy to step debug lucee, you just run /bin/tomcat/catalina.bat jpda run (or .sh)

I usually edit the catalina bat to make suspend default to y and the bundled vscode debugging profile defaults to port 5000, rather than 8000

When you are working with java directly in Lucee, your code should only interact with the loader interfaces, i.e. the APIs exposed in our javadocs, they are stable, that’s what the extensions use.

https://www.javadoc.io/doc/org.lucee/lucee/7.0.0.242-RC/index.html

The using the loader API guarantees that any underlying changes to the Lucee core won’t break your code, it’s also why Lucee, unlike Adobe Coldfusion, can easily be upgraded between major versions, without a full uninstall etc.

The loader interface has been stable for a long time, with 7 we have revved the loader interface version, adding the AI interfaces and merging the various hacks we have added overtime, back into the loader interfaces, to work around the limitations it causes.

As you can see from the bytecode, you’ll find the pageContext is the gateway to accessing other variables, functions, mappings, logging etc from the current request.

https://www.javadoc.io/doc/org.lucee/lucee/latest/lucee/runtime/PageContext.html#<init>()

To access BIFs, you can use loadBIF()

https://www.javadoc.io/doc/org.lucee/lucee/latest/lucee/runtime/util/ClassUtil.html#loadBIF(lucee.runtime.PageContext,java.lang.String)

For examples, just search the Lucee org on GitHub

As you can see the various Lucee extensions are a good reference guide on how to interact with Lucee via the stable loader interface

1 Like