Java223 Scripting - Script with Java on openHAB [5.0.1.0,6.0.0.0)

It’s not an issue, merely a deep debug log (this is why I used the trace log level).
The bundle tries to inject openHAB binding value in the script class, and for doing so, checks all class members (by their name). As the name (here, “logger”), doesn’t correspond to anything in the available openHAB bindings values, it logs a trace.

It could be null if the user doesn’t use the generated class “Items” as expected (for example, the user instantiate the Items class by himself instead of using the automatic injection provided by the bundle).
Therefore, I made this log suggesting the user to use the proper way.

You are right : there is no details when having an error using a transformation in a sitemap.
But, when using a transformation profile script, the script root error cause is properly logged. So this is probably a little omission.

As it is a global openHAB issue, I made a PR for openhab-core.

Bad indentation with method.invoke

When I create an MQTT thing with the file i.things (below), the file automation/lib/java/helper/generated/mqtt/MQTTActions.java contains:

            Method method = thingActionClass.getMethod("publishMQTT", String.class, byte[].class);
method.invoke(thingActions, p1, p2);

There is no indentation on the second line.

ON from the default preset is missing; no erros logged when transformationPattern= cannot be compiled

I have installed OpenHAB 5.1.0 build 4828, which includes Details error message in log when, in an UI transformation, a script is the error cause. by dalgwen · Pull Request #5028 · openhab/openhab-core · GitHub. I create things/i.things

mqtt:broker:b "Q" [host="127.0.0.1", password="1", username="a", clientID="oh5"]
Thing mqtt:topic:i "I" (mqtt:broker:b) {
  Channels:
    Type switch : a "A" [stateTopic="i1/s", transformationPattern="DSL:|return ON;"]
}

and items/a.items:

Switch always_on {channel="mqtt:topic:i:a"}

This works as expected. Now I change "DSL:|return ON;" to "JAVA:|return OFF;". The value of the item does not change. I have to write instead "JAVA:|return OnOffType.OFF;". But according to JSR223 Scripting | openHAB the value OFF is part of the default preset. I assume ScriptEngineFactory.scopeValues() has to be overriden to get everything from the default preset.

The other problem is that no error appears in openhab.log, even if transformationPattern="JAVA:|return OFF;" cannot be compiled.

Removing the MQTT broker and things leaves generated/mqtt/MQTTActions.java

Now I remove things/i.things. openhab.log contains:

2025-09-17 12:40:13.724 [DEBUG] [.internal.Java223ScriptEngineFactory] - Added/updated thing: Thing 'mqtt:topic:i' has been removed.
2025-09-17 12:40:13.726 [DEBUG] [ernal.codegeneration.SourceGenerator] - Scheduling Things generation in 10000 ms
2025-09-17 12:40:13.728 [DEBUG] [ernal.codegeneration.SourceGenerator] - Scheduling Actions generation in 10000 ms
2025-09-17 12:40:23.728 [DEBUG] [ernal.codegeneration.SourceGenerator] - Generating things
2025-09-17 12:40:23.729 [DEBUG] [ernal.codegeneration.SourceGenerator] - Generating actions
2025-09-17 12:40:23.742 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/Things.java
2025-09-17 12:40:23.766 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/Actions.java

but generated/mqtt/MQTTActions.java is still present. Just Actions.java is changed with(out) import helper.generated.mqtt.MQTTActions; … public MQTTActions getMqtt_MQTTActions(String thingUID) {return new MQTTActions(actions, thingUID);}. Similarly for Things.java. I think if the generated/ code does not make use of MQTT, because the system has no MQTT brokers and things, the directory generated/mqtt/ should be deleted.

instanceReuse

After reading README for me it is unclear how instanceReuse works. Is it applicable to inline transformations, like "JAVA:|return \"A\";"? Or is it applicable to all kinds of .java scripts, including transformations, but excluding automation/jsr223/*.java files? Also what means “Take note that it uses the compilation cache, managed by openHAB on best effort. So instance reuse is not guaranteed.”? As the caffeine cache is gone, what means “best effort”? Is the seconds sentance (not guaranteed) still relevant? How can I intentionally create a concurrent problem with instanceReuse?

[TRACE] [ation.java223.common.BindingInjector] - Cannot find an element with the key logger

It’s not an issue, merely a deep debug log (this is why I used the trace log level).
The bundle tries to inject openHAB binding value in the script class, and for doing so, checks all class members (by their name). As the name (here, “logger”), doesn’t correspond to anything in the available openHAB bindings values, it logs a trace.

I do not think it is a good idea to log information for irreparable things. Logging should be used for debugging or removing problems, but here something is just logged - causing distraction - and there is nothing useful, which can be done with the information.

null checks in generated/Items.java

When will itemRegistry be null?

It could be null if the user doesn’t use the generated class “Items” as expected (for example, the user instantiate the Items class by himself instead of using the automatic injection provided by the bundle).

How can the user instantiate the Items class by himself?

I just fixed these two on my branch. The second one was a bad import in the wrapper script (should have the ‘static’ modifier).

It’s an generic openHAB issue. From my tests, it seems there is no error log when the transformation is on the Thing definition side. When it is on the Item side, it “works”. Example:
Switch always_on {channel="mqtt:topic:i:a" [profile="transform:JAVA", toItemScript="|return OFF;"]}
With the above configuration, I do have an error log.
The error log is missing when using transformationPattern in a Thing. And with other languages as well (DSL in my test).
I think you can open an issue for it on openHAB core (or a PR if you want to tackle it yourself)

Done on my branch. Will do a release later.

It is applicable to all kind of scripts that can run several times, so, including profile, transformation, etc. (In fact, it matches with everywhere openHAB keeps a compiled script in its cache).
So, as you guessed, everywhere but “automation/jsr223/*.java” (as a script here runs only once).
But take note that for one-liner, (often used in transformation) it is nearly useless : as you cannot declare class fields in them, you cannot use them to share value between executions.
I added the above in the README, and also something about the cache:

Take note that it depends on the script compilation cache, managed by openHAB. So instance reuse is not guaranteed if something triggers openHAB to ask for a recompilation of your script (for example, a dependency change).

For example, several events trigger the same script, all at the same time (chances increase as the script takes a longer time to run). As those scripts share the same instance, you can then have classical java concurrency issues (race condition, etc.). It happened to me once.

I respectfully disagree: it is useful, as I already used this log to debug an issue during the bundle development (in fact, I added it specifically for it). And I don’t think it is polluting, as trace level logs has no reason to be enabled for the end user. I see them as information for me, developer, not for the end user (unless, as a maintainer, I ask the end user to enable it, to help me debug his issue).

If the users didn’t read the doc thoughtfully (and I think I can understand, as it is quite long), he can have the bad idea to write something like this :

Items myItems = new Items();

And then complains on this forum that it doesn’t work as he expected.
So to prevent me some headaches, I included a log to tell him he has done it wrong.

.java is “wrong extension”

In SourceWriter.processWatchEvent() if kind is not DELETE or MODIFY, then irrespective of everything else the logged error is that the file extension is wrong. This is not correct - the extension is right. When I create an MQTT broker, Java223 logs:

2025-09-18 19:00:11.350 [TRACE] [internal.codegeneration.SourceWriter] - Received ‘CREATE’ for path ‘/etc/openhab/automation/lib/java/helper/generated/mqtt/MQTTActions.java’ - ignoring (wrong extension)

Sometimes logging before throwing ScriptException, sometimes not logging

Provided that OH-core is supposed to log all received ScriptExceptions, why does internal/Java223ScriptEngine.java:invokeFunction() log before throwing an exception:

logger.warn("Method {} cannot be called by ScriptLoaded/ScriptUnloaded trigger", method.getName());
throw new ScriptException(e);

instead of just throwing an ScriptException, as it is done on all other places with throw new ScriptException, with the other place logging before throwing being strategy/Java223Strategy.java:execute()?

NoClassDefFoundError: java223/common/ScriptUnloadedTrigger

I put org.openhab.automation.java223-5.0.1.jar under /usr/share/openhab/addons, then create a sitemap/cd.sitemap:

sitemap cd label="B3" {
  Text item=x label="UU [JAVA(|return \"MMMX\";):%s]"
}

and load /basicui/app?sitemap=cd. Now I do touch /usr/share/openhab/addons/org.openhab.automation.java223-5.0.1.jar and then change the settings of Enable Helper Library from ON to OFF → Save. Now I reload /basicui/app?sitemap=cd. The browser shows:

HTTP ERROR 500 java.lang.NoClassDefFoundError: org/openhab/automation/java223/common/ScriptUnloadedTrigger
URI:	/basicui/app
STATUS:	500
MESSAGE:	java.lang.NoClassDefFoundError: org/openhab/automation/java223/common/ScriptUnloadedTrigger
SERVLET:	/basicui/app
CAUSED BY:	java.lang.NoClassDefFoundError: org/openhab/automation/java223/common/ScriptUnloadedTrigger
CAUSED BY:	java.lang.ClassNotFoundException: org.openhab.automation.java223.common.ScriptUnloadedTrigger cannot be found by org.openhab.automation.java223_5.0.1
Powered by Jetty:// 9.4.57.v20241219

and

openhab.log contains
2025-09-23 12:19:51.320 [DEBUG] [.internal.Java223ScriptEngineFactory] - bundle org.openhab.automation.java223:5.0.1 (248)[org.openhab.automation.java223.internal.Java223ScriptEngineFactory(440)] : ImmediateComponentHolder Will not enable component for pid automation.java223: holder enabled state: true, metadata enabled: true 
2025-09-23 12:19:52.306 [TRACE] [internal.codegeneration.SourceWriter] - Received 'DELETE' for path '/etc/openhab/automation/lib/java/convenience-dependencies.jar' - ignoring (wrong extension)
2025-09-23 12:19:52.306 [DEBUG] [23.internal.strategy.Java223Strategy] - From watch event DELETE /etc/openhab/automation/lib/java/helper-lib.jar
2025-09-23 12:19:52.307 [INFO ] [al.strategy.jarloader.JarFileManager] - Full rebuild of java223 classpath
2025-09-23 12:19:52.308 [DEBUG] [al.strategy.jarloader.JarFileManager] - Libraries to load from '/etc/openhab/automation/lib/java' to memory: []
2025-09-23 12:19:52.309 [TRACE] [internal.codegeneration.SourceWriter] - Received 'DELETE' for path '/etc/openhab/automation/lib/java/helper-lib.jar' - ignoring (wrong extension)
2025-09-23 12:19:53.927 [WARN ] [org.eclipse.jetty.server.HttpChannel] - /basicui/app
java.lang.NoClassDefFoundError: org/openhab/automation/java223/common/ScriptUnloadedTrigger
	at org.openhab.automation.java223.internal.Java223ScriptEngine.invokeFunction(Java223ScriptEngine.java:152) ~[?:?]
	at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.removeEngine(ScriptEngineManagerImpl.java:201) ~[?:?]
	at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.createScriptEngine(ScriptEngineManagerImpl.java:115) ~[?:?]
	at org.openhab.core.automation.module.script.ScriptTransformationService.transform(ScriptTransformationService.java:174) ~[?:?]
	at org.openhab.core.ui.internal.items.ItemUIRegistryImpl.transform(ItemUIRegistryImpl.java:660) ~[?:?]
	at org.openhab.core.ui.internal.items.ItemUIRegistryImpl.getLabel(ItemUIRegistryImpl.java:504) ~[?:?]
	at org.openhab.ui.basic.internal.render.AbstractWidgetRenderer.preprocessSnippet(AbstractWidgetRenderer.java:135) ~[?:?]
	at org.openhab.ui.basic.internal.render.AbstractWidgetRenderer.preprocessSnippet(AbstractWidgetRenderer.java:115) ~[?:?]
	at org.openhab.ui.basic.internal.render.TextRenderer.renderWidget(TextRenderer.java:57) ~[?:?]
	at org.openhab.ui.basic.internal.render.PageRenderer.renderWidget(PageRenderer.java:193) ~[?:?]
	at org.openhab.ui.basic.internal.render.PageRenderer.processChildren(PageRenderer.java:159) ~[?:?]
	at org.openhab.ui.basic.internal.render.PageRenderer.processPage(PageRenderer.java:124) ~[?:?]
	at org.openhab.ui.basic.internal.servlet.WebAppServlet.service(WebAppServlet.java:176) ~[?:?]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:584) ~[bundleFile:4.0.4]
	at org.ops4j.pax.web.service.spi.servlet.OsgiInitializedServlet.service(OsgiInitializedServlet.java:102) ~[?:?]
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) ~[?:?]
	at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1656) ~[?:?]
	at org.ops4j.pax.web.service.spi.servlet.OsgiFilterChain.doFilter(OsgiFilterChain.java:113) ~[?:?]
	at org.ops4j.pax.web.service.jetty.internal.PaxWebServletHandler.doHandle(PaxWebServletHandler.java:334) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) ~[?:?]
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600) ~[?:?]
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235) ~[?:?]
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) ~[?:?]
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) ~[?:?]
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:505) ~[?:?]
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) ~[?:?]
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355) ~[?:?]
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[?:?]
	at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:234) ~[?:?]
	at org.ops4j.pax.web.service.jetty.internal.PrioritizedHandlerCollection.handle(PrioritizedHandlerCollection.java:96) ~[?:?]
	at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:731) ~[?:?]
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[?:?]
	at org.eclipse.jetty.server.Server.handle(Server.java:516) ~[?:?]
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487) ~[?:?]
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732) ~[?:?]
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479) ~[?:?]
	at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:439) ~[?:?]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:137) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.http2.HTTP2Connection.produce(HTTP2Connection.java:193) ~[?:?]
	at org.eclipse.jetty.http2.HTTP2Connection.onFillable(HTTP2Connection.java:148) ~[?:?]
	at org.eclipse.jetty.http2.HTTP2Connection$FillableCallback.succeeded(HTTP2Connection.java:371) ~[?:?]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) ~[?:?]
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:555) ~[?:?]
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:410) ~[?:?]
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:164) ~[?:?]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) ~[?:?]
	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) ~[?:?]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) ~[bundleFile:9.4.57.v20241219]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) ~[bundleFile:9.4.57.v20241219]
	at java.lang.Thread.run(Thread.java:1583) [?:?]
Caused by: java.lang.ClassNotFoundException: org.openhab.automation.java223.common.ScriptUnloadedTrigger cannot be found by org.openhab.automation.java223_5.0.1
	at org.eclipse.osgi.internal.loader.BundleLoader.generateException(BundleLoader.java:541) ~[org.eclipse.osgi-3.18.0.jar:?]
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass0(BundleLoader.java:536) ~[org.eclipse.osgi-3.18.0.jar:?]
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:416) ~[org.eclipse.osgi-3.18.0.jar:?]
	at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:168) ~[org.eclipse.osgi-3.18.0.jar:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:526) ~[?:?]
	... 62 more
2025-09-23 12:20:07.490 [DEBUG] [ernal.codegeneration.SourceGenerator] - Generating things
2025-09-23 12:20:07.492 [DEBUG] [ernal.codegeneration.SourceGenerator] - Generating actions
2025-09-23 12:20:07.493 [DEBUG] [ernal.codegeneration.SourceGenerator] - Generating items
2025-09-23 12:20:07.579 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/Things.java
2025-09-23 12:20:07.599 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/Items.java
2025-09-23 12:20:07.649 [TRACE] [ernal.codegeneration.SourceGenerator] - Processing class 'MQTTActions' in package 'org.openhab.binding.mqtt.internal.action'
2025-09-23 12:20:07.663 [TRACE] [ernal.codegeneration.SourceGenerator] - Found method 'publishMQTT' with parameters '[String, byte[], Boolean]' and return value 'void'.
2025-09-23 12:20:07.664 [TRACE] [ernal.codegeneration.SourceGenerator] - Found method 'publishMQTT' with parameters '[String, String]' and return value 'void'.
2025-09-23 12:20:07.665 [TRACE] [ernal.codegeneration.SourceGenerator] - Found method 'publishMQTT' with parameters '[String, String, Boolean]' and return value 'void'.
2025-09-23 12:20:07.666 [TRACE] [ernal.codegeneration.SourceGenerator] - Found method 'publishMQTT' with parameters '[String, byte[]]' and return value 'void'.
2025-09-23 12:20:07.681 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/mqtt/MQTTActions.java
2025-09-23 12:20:07.694 [DEBUG] [internal.codegeneration.SourceWriter] - Wrote generated class: /etc/openhab/automation/lib/java/helper/generated/Actions.java
2025-09-23 12:20:08.583 [TRACE] [internal.codegeneration.SourceWriter] - Received 'CREATE' for path '/etc/openhab/automation/lib/java/helper/generated/Things.java' - ignoring (wrong extension)
2025-09-23 12:20:08.601 [TRACE] [internal.codegeneration.SourceWriter] - Received 'CREATE' for path '/etc/openhab/automation/lib/java/helper/generated/Items.java' - ignoring (wrong extension)
2025-09-23 12:20:08.684 [TRACE] [internal.codegeneration.SourceWriter] - Received 'CREATE' for path '/etc/openhab/automation/lib/java/helper/generated/mqtt/MQTTActions.java' - ignoring (wrong extension)
2025-09-23 12:20:08.695 [TRACE] [internal.codegeneration.SourceWriter] - Received 'CREATE' for path '/etc/openhab/automation/lib/java/helper/generated/Actions.java' - ignoring (wrong extension)

Caching in .class files

Is it possible for all .java files under /openhab/automation/jsr223 and automation/lib/java to create a corresponding .class file, whenever a .java file is changed? Possibly deleting the .class file, when the .java file is changed to something invalid? And when starting OpenHAB to use the .class files, instead of compiling the .java files, for faster booting?

As location also /var/lib/openhab/.cache can be used, this directory is currently utilized by GraalVM. I am not aware if other scripting/automation engines in OpenHAB cache compiled scripts over OH restarts.

throw new Java223Exception("Cannot instantiate the script", e) - e is not logged

I create in automation/jsr223/n.java:

public class N {
    N () {}
    public static Object main() {
        return null;
    }
}

openhab.log gets:

2025-09-23 13:49:29.418 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script ‘/etc/openhab/automation/jsr223/n.java’: Cannot instantiate the script
2025-09-23 13:49:29.419 [WARN ] [ort.loader.AbstractScriptFileWatcher] - Script loading error, ignoring file ‘/etc/openhab/automation/jsr223/n.java’

because the constructor is private. The problem is that in Java223Strategy.construct():throw new Java223Exception("Cannot instantiate the script", e) e is not logged.

About instanceReuse

For me this instanceReuse is still unclear. I think it would be very useful to include one example utilizing instanceReuse, where a value is shared between executions; one counter-example, showing how it fails with concurrency; and once again the last example with thread-synchronization, which shows how to utilize instanceReuse with concurrency correctly.

[automation] Fully synchronize script action/condition execution if supported by engine by florian-h05 · Pull Request #4426 · openhab/openhab-core · GitHub synchronizes the executions in OH-core, if the script engine supports Lock. Maybe this is relevant here and a lock is missing in Java223?

lib/java/.java files are not reloaded, when changed; and not loaded when created

I create a file automation/lib/java/u.java:

public class U {
    public static String v() {
        return "U";
    }
}

Openhab.log contains:

Received ‘CREATE’ for path ‘/etc/openhab/automation/lib/java/u.java’ - ignoring (wrong extension)

I change above "U" with "U1", save the file, now the file is not ignored.

I create the file transform/m.java:

public class M {
    public Object main() {
        return U.v();
    }
}

and sitemap/cd.sitemap:

sitemap cd label="B3" {
  Text item=x label="UU [JAVA(m.java):%s]"
}

Loading it shows U1. Now I change in u.java U1 with U2 and reload the sitemap. It still shows U1. I have to modify somehow m.java so that the content of u.java is reloaded. I think this is exactly the problem described at [jsscripting] User Libraries, once modified, can no longer be invoked until scripts are re-enabled or re-saved · Issue #19091 · openhab/openhab-addons · GitHub and fixed by https://github.com/openhab/openhab-core/pull/4922.

I don’t know nearly enough of how this Java integration works to say much, but I’d just like to add that there’s a good reason that JavaScript must be synchronized externally: JavaScript doesn’t support threading it all, it doesn’t know what a thread is, and always runs in a single thread.

Java, on the other hand, can handle threads, and there’s also a huge performance limitation in forcing everything to be single-threaded, so it might be beneficial to let Java handle synchronization instead of going down the “training-wheels route” of JavaScript.

The log is erroneous. I changed it to
Received '{}' for path '{}' - ignoring (wrong extension or unused event kind)
(This is a trace level log, not an error per se)

Regarding all log issues you mentioned (sometimes the stack trace is here, sometimes not):
As you demonstrated, openHAB handling and logging of exception is not very consistent depending on the script origin. So I chose to change it nearly everywhere. Now I systematically log an error with the stack trace (so, full info), and throw to openHAB only a simple message description. Hopefully it will work around openHAB weak points on this regard.

Wow, you hit some low level issue here. I think, by touching the jar, you managed to unload/reload the java223 module and throw the openHAB script engine manager into an unknown state. I think this kind of error is very dependent on the openHAB bundle management system, and maybe even OSGi related, and I’m not knowledgable enough to tackle into it :smiling_face_with_tear:
(And even so, honestly probably too many hours of bug tracking for little gain on this edge case.)

Is it possible for all .java files under /openhab/automation/jsr223 and automation/lib/java to create a corresponding .class file, whenever a .java file is changed

Yes, but… It is not the way I designed it.
I think I remember another java bundle did it this way. It is totally OK, but for this bundle I chose to use an in memory compiled class system for a big code simplification. I don’t have other arguments for this (it is already a big one in my opinion :joy:), and if someone make a good PR with a system like this, I’m not opposed to merge it. But it’s a many hours trip.
I’m curious of the performance hit on startup, though. Starting openHAB shouldn’t be very frequent, and recompiling scripts at startup shouldn’t be a big problem unless you have a really massive amount of code ?

Should be fixed by the log policy overhaul.

For me this instanceReuse is still unclear

I added new information about the inner work of the bundle and script instantiation in the README.

I think I disagree a little with you at the level of details needed. For two reasons :

  • It’s a niche issue (the fact that I manage to stumble upon it is still a surprise to me)
  • It is not related to this bundle on particular, but about programming. Threading and concurrency is a standard programming issue (not even Java related).

I think that the documentation is already big enough :sweat_smile:
But I agree on the fact that I say either too much or too less. So I added some precision in the README, to let people know the subject exist, and ask them to seek for specialised resources.

Hum, I may be wrong, but I think it is not the same.
It seems another case of openHAB handling scripts differently by the origin.
Changing a lib dependency SHOULD force recompilation of script that declares depending on this library. It works for “standard” script (i.e. I tested a script created from the GUI in the “script” section, and it runs every time with updated lib), but it doesn’t work for a transformation script.
I debugged a little, and I found that transform scripts are not registered in the dependency tracking system, at all. So unless I misunderstood something, it can’t work.
I don’t use other JSR223 services, but by reading code I’m pretty sure it won’t work for them either.
(if interested, in ScriptModuleHandlerFactory, Action and Condition modules are registered, but transformation seems completely out of scope, they are not tracked in the trackedHandlers list)

So javascript is like python and its infamous GIL (I don’t want to be a troll, but…)
Then we all agree on this, we should let to the end-user the responsibility of synchronising code.

Interesting, thank you for the insight :+1:

1 Like

Though the event loop allows for concurrency in a style I would compare a bit to virtual threads/reactive programming (in the way it’s handled by the language itself and not the OS).

In fact NashornJS allows for multi threaded access to the script context, which requires the user to synchronise to avoid concurrency issues.
GraalJS hence decided to go the path and completely disallow multi threaded access to its context which requires the extensive amount of synchronisation that we are doing.

This doesn’t give any meaning to me, sorry. I don’t know reactive programming, but if you mean Java virtual threads, that’s something else entirely. Java virtual threads must be used like any other thread, with the usual concurrency protection in place. What’s different about them, is that behind the scenes, one thread can do the work of multiple virtual threads if the virtual threads are blocking. So, it’s just a way to “save” the number of real threads that are created/allocated.

Wherever I look, I find the same answer: JavaScript is single-threaded. I don’t understand how the event loop can allow for concurrency when the language doesn’t have any concept/knowledge about concurrency.

When JS is used as a “scripting language extension” for Java, which is multithreaded, multiple threads can end up calling scripts, obviously. What I don’t understand is how you synchronize from JS, since the concept doesn’t exist. Do you somehow have to acquire Java objects and use them as monitors by calling their Java methods?

Don’t get me wrong, I don’t think it’s a bad thing to synchronize calls to JS, I see it as the only “sane” thing to do when you can’t handle it in JS itself.

Promises and setTimeout/setInterval allow for concurrency. They run on a single thread in the event loop, which basically executes the Promise resolve or reject code once the promise completes. The same for timeout and interval. While waiting for the promise to complete, other code can be executed. And I/O is usually non-blocking.
So we can call the event loop concurrency, but sure, its no parallelism.
Its still able to handle a hell of load despite being single-threaded.
I would hence compare the event loop a bit like having multiple virtual threads on a single carrier thread.
And Node.js CAN also do parallelism.
Just FYI all the server side rendering of Next (React) and Nuxt (Vue) is usually done in Node.js, and openHAB Cloud is also using Node.js.

We have to synchronise before entering the JS context, so all synchronisation is done in Java. The OpenhabGraalJSScriptEngine implements the Lock interface and thereby exposes access to its internal lock, so core can synchronise in ScriptActionHandler and ScriptConditionHandler, and in case of SimpleRule there is some wrapping done inside the add-on to synchronize on the lock.
Timer execution is also synchronized through some wrapped code.

Maybe we can remove some of that wrapping in the add-on code and instead move synchronisation into the original core code now that we have the Lock implementation for script engines. The wrapping was there first, if I had to do it today (didn‘t to this originally) I would probably do it differently.

I don’t consider promises and timers concurrency - it’s “the asynchronous” way to do things, where you have intricate ways to decide what gets done when. But it’s still very much single threaded, it can’t use modern multicore CPUs efficiently, and it can’t actually do multiple things at once. Hence, everything is done in a single thread, and locks/synchronization isn’t a thing to worry about. But, working asynchronously gets complicated quickly when the complexity of the tasks increases, so I think that the “async approach” has obvious limitations to go beyond the fact that the code can’t utilize multiple cores.

As I see it, JS has tried to “push the boundries” what what can be done within the limitations of being single threaded with Promises and all the other stuff that “surrounds” this, but this can only take you so far. It will always suffer for/be limited by not supporting concurrency, but the upside is of course that the “developer requirements” needed to work with it are much lower, since concurrency is typically something many people struggle to handle correctly.

In many ways I think the lack of concurrency is what makes langauges like JS and Python popular, it requires much less knowledge to start using, so it’s appealing for that reason. At the same time, it’s the same thing that makes them popular that will always hold the back and prevent them from being able to handle larger projects, together with some other drawbacks like the lack of type safety of course.

1 Like

Item transformations, which are using the same script file, could also run concurrently on the same scriptengine instance. This scenario, is not really script language specific. Thats the reason why i use locks in pythonscripting too.

1 Like

I don’t know exactly how JSR223 works, but I would imagine that any thread in Java could invoke some JSR223 “function” that involves the script engines. That wouldn’t be a problem if you could handle synchronization from the script languages (although, it might still be too complicated for users to handle), but if you don’t have access to the synchronization mechanisms from the script language, it’s not possible to handle at all, and synchronizing (aka forcing only one thread at a time to access the scripting engine) is really the only sensible thing to do.

the root cause for the lock, is the lazy engine initialization. This must happen during the first usage, because it depends on some data which are only available at this time.

for that reason it happens inside the function beforeInvocation which is triggered before each ‘eval’ call.

I think it is acceptable, because it affects only the compile and initial script evaluation. That means a compiled transformation script or an evaluated rule script is using the lock only during the “init/load phase”.

Python has threads.

It was. Prior to OH 3.0 users had to implement synchronization themselves. It was a nightmare. There are other changes which would make it less of a nightmare now, but it was still horrible. The average OH user does not know enough about programming to use synchronization themselves safely.

Absolutely, that is no problem. After initialization, it will just hold this lock extremely briefly and then release. But, it still means that the code is “unprotected” when accessing shared objects while running. I’m not sure of the implications here, but it sounds to me like this would allow multiple Python script instances to be running at once, accessing the same objects, with no way to synchronize access to them..?

I haven’t dabbled with Python since 2005, so my knowledge is somewhat outdated and dusty. But, a quick search gives me this:

My interpretation of this is that, yes, while it technically “has threads”, only one is allowed to run at a time, the others are blocked, so there is no concurrency. So, OK, maybe it’s wrong to call it “single-threaded”, maybe “serial-threaded” is better, but the effect is much the same - no actually concurrency occurs.

:+1:

I’m not defending the locks at all. I would be happy if we could eliminate them.

We just need a way to “finalize” the initialization like now in “beforeInvocation” with all needed data available.

— UPDATE —

and I was wrong with that. I checked it right now.