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

This automation bundle is openHAB-JSSR223 compliant and allows you to write your script in Java.

Alpha release, no “published” tag. Published ! Many thanks to you if you want to test.
Comments welcome.

(For the why and the history, see the second post below.)

Main feature:

  • full JSR 223 support (use in files, in GUI, transformations, inline rule action, etc…)
  • auto injection of OpenHAB variable/preset for simplicity
  • library support for sharing code (.jar and .java)
  • rule annotations available in the helper library for creating rules the easiest way
  • helper library files auto generation for items, things, and actions, with strong typing and ease of use
  • cache compiled scripts in memory for blazingly fast executions after the first one (sub millisecond overload)
  • no boilerplate code for simple script: you can do a one liner script, as declaring a class and a method is optional.
  • optional reuse of instances script to share values between execution occurrences
  • designed to be easily used with your favorite IDE, with very little configuration

How to use it ? See documentation on the last link of this post.

Changelog

Version BETA4

  • remove custom caching system to use openHAB’s one
  • fix error when using one line script with no helper lib
  • workaround an openHAB bug when starting the bundle after an “clean cache” operation
  • delete auto generated helper library when the related parameter is disabled

Version BETA3

  • openHAB 5 support
  • Add a boolean to enable/disable helper lib generation
  • fix : bad detection of library deletion and regeneration if needed

Version BETA2

  • fix: remove generic parameters in generated getMethod() reflection call (thanks Ardanedh)
  • fix: Wrong ChannelEvent parameter type
  • new parameter (startupGuardTime) for delaying helper generation at startup if the file already exists

Version BETA1

  • makes action initialization more reliable
  • allow custom rule uid
  • accomodate around the new openHAB functionnality “compile once” by still allowing cache invalidation when a lib changes

Version ALPHA1

  • initial release

Resources

JAR Bundle
Source and documentation

4 Likes

History

There are already three other bundles more or less available allowing you to use java in OpenHAB, so why another one ?

First, many thanks to them, especially @J-N-K for the SmartHome/J implementation, from which I took a good share of code. I kept author names in some classes to reflect that. The 3 other bundles are:

I decided to do another one, mainly for these two requirements :

  • a bundle available on the main OpenHAB marketplace, and maybe merged as an official add-on in some time
  • fully compatible JSR223

So I started by proposing small features to @weberjn bundle. But I didn’t stop at “small” and had other ideas, which was going against its preference of keeping the bundle simple. So I forked for myself. Then I learned about the SmartHome/J bundle (I wasn’t aware of it before), and took some code and ideas from it (but with a very different compiling architecture)

Then, I kept piling new features:

  • a cache for very fast execution and reuse of instance (allowing sharing value between executions)
  • a very clear separation between the way you can write your Java scripts (no dependencies at all, plain java class), the bundle, and the helper library which is “just” a library that you could have written yourself and that can evolve independently.
  • a ‘no boilerplate’ option for one liner script
  • many quality of life improvement (a smart injection of openHAB values, some effort for flexibility to propose different coding ways, some default values here and there to shorten lengthiness or complexity of code, etc.)
  • and other features… (See documentation)

Now the base code is waaayyy different from all of those, and thus, a new bundle. I’m not sure if it is better (probably not, and this bundle won’t have all the features the other ones can have, I think for example JRule has many helper functions), but the reverse is certainly true : it has its own strong arguments that others don’t have.

If this bundle doesn’t meet a public, I will of course remove it from the marketplace, as I don’t want to add useless confusion to this already many-bundle situation.

5 Likes

Nice. Just had a brief look at the code (I personally switched to JavaScript). If you want to open a PR in openhab-addons, Please ping me and I’ll try to assist with the review process.

5 Likes

Nice bundle, I’m currently using Jan’s Java Rule Automation addon (which runs smoothly) and thought I’d give your version of the JSR-223 implementation a spin, too.

I’ve encountered a problem in my openHAB setup and created a PR on github - the PR is quick and dirty, touching as few lines of your code as possible. I’ve you’d like a more refined version, let me know.

As I said in the PR, thanks !
I made a new release that include your fix.

1 Like

The file https://github.com/dalgwen/openhab-addons/releases/download/4.3.0_SNAPSHOT-java223-BETA2/org.openhab.automation.java223-4.3.0.jar , when unzipped, contains in src/org/eclipse/jdt/annotation some .java files like NonNullByDefault.java . The repository GitHub - openhab/openhab-addons: Add-ons for openHAB does use the NonNullByDefault annotation on many places, across the other add-ons but does not include the NonNullByDefault.java file. Is including these .java files in the .jar necessary? At the same time annotation/Checks.java (and .class) is also included, but the Checks annotation is not used anywhere.

The unzipped java file contains in the root directory bundle.properties with “bundleName=JDT Annotations for Enhanced Null Analysis”. I guess this is not necessary or incorrect. Likewise for about.html - the automation.groovy add-on jar file has no about.html and bundle.properties files.

I am currently using Groovy, because out of all other standard JSR223 plugins for OpenHAB Groovy has the smallest jar size - Comparing different automation languages . DSL is not feature complete - it does not allow to define a variable outside a rule - DSL global functions ignore global variables · Issue #3696 · openhab/openhab-core · GitHub . So compared to Groovy, whose jar is 7.1 MB, org.openhab.automation.java223-4.3.0.jar is 2.7MB. Also compared to Groovy, Groovy dispatches all methods dynamically, while JAVA-JSR223 is supposed to dispatch these statically, improving further performance. That said, for optimal performance and fast boot, I am looking for a way to write rules in Java, without loading any OH helper libraries - Groovy does not provide such and I am perfectly okay with the lack of helper libraries.

Groovy and JavaScript/GraalVM JSR223 in OpenHAB have the advantage, that they allow writing scripts. Just code, which is executed sequentially, including class definitions, which code does install rules, writes logs and so on, but it allows to write a program, not just rules. My understanding is that the Java223 add-on allows to define a static main method, which does the same trick. However the author of java-scriptengine - which executes .java files, has created one more JSR223 variant jshell-scriptengine. The latter executes code, as if it is implicitly wrapped in the main method. Would it be reasonable, that is skip writing explicit main method, to use jshell-scriptengine instead of java-scriptengine here?

JavaScript/GraalVM Add-on offers a helper library, which can be loaded implicitly, but does not have to be. There is a toggle in the MainUI/Settings to adjust whether the helper library should or should not be loaded by OpenHAB. It would be good if JAVA-JSR223 also offers such a toggle to enable/disable loading the helper library, which I guess is helper-lib.jar. I also do not want to use the ready-to-use libraries in the automation/lib/java path (Items.java, Things.java, etc): in Groovy I am fine without these. So I prefer if there is an option to disable generating these.

My understanding of the purpose of convenience-dependencies.jar is that it simplifies programming in some IDEs, compared to importing dependencies from the OpenHAB source code. In fact convenience-dependencies.jar contains classes like org/openhab/core/items/GenericItem.class, org/openhab/core/items/Item.class, etc. I do not need convenience-dependencies.jar, so I would prefer if there is a Java223 preference to disable the installation of this file.

To sum up the last two paragraphs, I think there should be one or more option to disable generating content in openhab/automation/lib and by default content should not be generated.

For the Configuration paremeter scriptCacheSize the default is 50 and the type is text. 50 what? 50 classes, 50 megabytes? Why is the type not an integer, as is for stabilityGenerationWaitTime? What is the use case of this scriptCacheSize? Does it cache compiled files from openhab/lib/java/*.java? I mean, I do not understand why the system does not do what is meaningful here and skip this configuration parameter? Why does Groovy/OH work perfectly fine without such parameter?

Are there any benefits of using Java-JSR223 transformations compared to DSL transformations? DSL transformations are XText/XTend and are rewritten to pure Java. DSL transformations do not need the return keyword and have different form of the ternary operator. But otherwise it is the same. Can the return keyword be somehow made optional when writing JAVA-223 transformations?

I think the Java-JSR223 add-on is a very good thing: it allows writing full pledged logic, without having much run-time overhead for loading script interpreters. On this criterion it beats the other automation plugins: Groovy, Jython, Python(GraalVM), JavaScript(GraalVM), JRuby. But I think Java-223 can be made even better (for my use-case) by removing “all extra useful stuff”, like generating jar and java files, removing the Java223Script class, while keeping the injection mechanisms. This means also having much smaller org.openhab.automation.java223-4.3.0.jar, resulting faster load times and less RAM needed at run time. The Groovy OH-Add-On does not have this extra usefull stuff and does work. So I would be very glad if a Java-JSR223 plugin is offered, which executes java files, utilizes the presets and that’s all.

Currently the OpenHAB 5.0 MQTT add-on loads many sub-bundles, like Roovy, General MQTT, HomeAssistand schema. The latter is now implemented in Python(GraalVM), because Jinja works perfectly only in Python. This means, that using just MQTT one has to load Python(GraalVM), even if no HomeAssistant will be used by the MQTT thing. As mentioned at [mqtt.homeassistant] Use GraalPy and import actual Home Assistant templating code by ccutrer · Pull Request #18857 · openhab/openhab-addons · GitHub the plan for OpenHAB 5.1 is to split this into several submodules/Add-Ons, one of them just being the Generic MQTT thing.

All that said, it would be very good if the Java-JSR223 Add-On is split in two add-ons: one providing just execution of Java-files, equivalent to what Groovy does currently, injection, loading files from automation/lib/ (for the latter I am not sure); and the other adding the remaining features like code generation, helper library.

I think that the first add-on can be made very, very tiny. Then Blockly could be rewritten (not by me) to cope with Java rules instead of JavaScript rules and this would be so much faster and less memory hungry.

Hello,

Thank you for your comments and interest!

I don’t really remember, and I’m not really sure either, but it seems from one of my comment in the pom.xml that it is needed for the dynamic export of the Nullable class at runtime in the generated dependencies.jar. But I don’t remember why the “.java” file, and not only the classes.
As for why I included that in the convenience-dependencies.jar ? It’s for ease of use : a user using an IDE won’t have any other library to import (the non null annotation are used by the helper library, and so error will appears if not included)

I have absolutely no idea, sorry. Maybe a byproduct of the aforementioned inclusion ?

In fact I considered this, and even try a POC, but I miserably failed. As my first tries was two (?) years ago, I don’t remember the details now… And there is also the matter of performance : I remember having a discussion and someone pointed me that java-scriptengine has better performance. Sorry again, cannot remember where !

And I know this is a “hacky” way of resolving this use case, but I made a “no boilerplate code” functionality, to include code without the class and method declaration.

I really don’t know how the DSL/XTend tranformations work, so I can’t answer here. I never tried to learn it.
For the “return” keyword : as far as i know, there is no way to know if the script executed is for a transformation or for a full fledged script (openHAB script engine doesn’t give this information to the JSR bundle). So it will be a “guessing game” with some parsing, and even if it is possible, it would add unwanted complexity ( and thus a bigger surface for bug). I don’t know how (and if) the other JSR langage handle this ?

I think I’d rather go with the “optional helper generation” route. The trade-off of another bundle is, IMHO, to much (for me and my laziness, and also for other users). And ease-of-use is my first priority for this bundle.
I get that it’s not ideal or pretty to have unused file in some case, but unless I’m mistaken, it should not have any noticeable impact (cpu, ram, or disk).

I will go back to you after trying to make the helper part optional.

Interesting idea, but I would be surprised if anyone decided to do it.
javascript seems to be a good match for a script language used by the end user. Performance trade off is low.
Blockly generating javascript is a strong point : users can modify and get inspiration from it.
I love java, but outside professional developers, it is not well known and easy to learn.

1 Like

New version released, after a couple hours of work :

  • for openHAB 5
  • with the ability to disable helper library generation.
1 Like

Blockly won’t be rewritten by its maintainers as well.

You can get to know this out of the script context, mainly filename and script identifier, see openhab-addons/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/DebuggingGraalScriptEngine.java at 539f52715c0e8a5f4b96267fbaa8835bb69bc087 · openhab/openhab-addons · GitHub.

Nice, thank you! I will look into it :+1:

The cache is a huge performance boost : it prevents the engine from recompiling something that was previously compiled.
openHAB and the JSR script engine has such a functionality since OH4 (or maybe another version, I don’t remember). I added this initially before OH own implementation. But the OH implementation has a big flaw for the bundle I wanted to make : it can’t be managed from outside, and I needed to invalidate it if something in the library directory was modified.

Is this cache only beneficial when there are files in lib/java? Do you mean that the problem with invalidating the loaded data (user libraries) is the same as described here - [jsscripting] User Libraries, once modified, can no longer be invoked until scripts are re-enabled or re-saved · Issue #19091 · openhab/openhab-addons · GitHub ?

For comparison, Groovy for OH uses ClassLoader derivative to load things under openhab/automation/groovy - openhab-addons/bundles/org.openhab.automation.groovyscripting/src/main/java/org/openhab/automation/groovyscripting/internal/CustomizableGroovyClassLoader.java at main · openhab/openhab-addons · GitHub - and on its own it uses - groovy/src/main/java/groovy/lang/GroovyClassLoader.java at master · apache/groovy · GitHub - org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache for cache. The source code of the latter is at groovy/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java at master · apache/groovy · GitHub . It is 270 lines of Java code. Isn’t this 270 lines cache good enough, compared to the 2.0 MB of .class files under com/github/benmanes/caffeine/cache included in the Java223 bundle?

I think generateHelper is a better wording than "enableHelper”. The word “enable” only suggests that something is either on or off, but this is implied by the binary nature of this property. Can this adjustment be by default OFF? The problem with the initial ON is that once the add-on is installed all openhab/automation files are already generated and they need to be be deleted manually from the file system.

About transformations, I have an items/x.items with content String x, I set its value with curl -X POST --header "Content-Type: text/plain" --header "Accept: application/json" -d "BRUM" "http://127.0.0.1:8080/rest/items/x" and I have a sitemaps/cd.sitemap

sitemap cd label="B3" {
  Text item=x label="DSL    [DSL(|    return input +  \" DSL\";   ):%s]"
  Text item=x label="GROOVY [GROOVY(| return input +  \" GROOVY\";):%s]"
  Text item=x label="JAVA   [JAVA(|   return input.toString() +  \" JAVA\";):%s]"
}

openhab.log logs: 2025-09-03 18:26:47.378 [WARN ] [ui.internal.items.ItemUIRegistryImpl] - Failed transforming the value 'BRUM' with pattern 'JAVA(| return input.toString() + " JAVA" ;):BRUM': Failed to execute script. It does not help, when I remove .toString(). How are (inline) transformations supposed to be used? The DLS and GROOVY transforms do what they are supposed to do.

When I install the bundle in OH 5.1+ under /usr/share/openhab/addons/, set enableGeneration to false, stop OH, upgrade to OH 5.1.0 Build 4815, start OH, openhab.log contains:

2025-09-03 17:00:56.451 [ERROR] [.internal.Java223ScriptEngineFactory] - bundle org.openhab.automation.java223:5.0.0 (248)[org.openhab.automation.java223.internal.Java223ScriptEngineFactory(1)] : Error during instantiation of the implementation object
java.lang.reflect.InvocationTargetException: null
        at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(Unknown Source) ~[?:?]
        at java.lang.reflect.Constructor.newInstanceWithCaller(Unknown Source) ~[?:?]
        at java.lang.reflect.Constructor.newInstance(Unknown Source) ~[?:?]
        at org.apache.felix.scr.impl.inject.internal.ComponentConstructorImpl.newInstance(ComponentConstructorImpl.java:326) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.createImplementationObject(SingleComponentManager.java:286) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.createComponent(SingleComponentManager.java:115) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.getService(SingleComponentManager.java:1002) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.getServiceInternal(SingleComponentManager.java:975) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.getService(SingleComponentManager.java:920) ~[?:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceFactoryUse$1.run(ServiceFactoryUse.java:220) ~[org.eclipse.osgi-3.18.0.jar:?]
        at java.security.AccessController.doPrivileged(Unknown Source) ~[?:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceFactoryUse.factoryGetService(ServiceFactoryUse.java:217) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceFactoryUse.getService(ServiceFactoryUse.java:118) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceConsumer$2.getService(ServiceConsumer.java:48) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.getService(ServiceRegistrationImpl.java:547) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.getService(ServiceRegistry.java:534) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.getService(BundleContextImpl.java:660) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.apache.felix.scr.impl.manager.SingleRefPair.getServiceObject(SingleRefPair.java:88) ~[?:?]
        at org.apache.felix.scr.impl.inject.methods.BindMethod.getServiceObject(BindMethod.java:675) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager.getServiceObject(DependencyManager.java:2612) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager.doInvokeBindMethod(DependencyManager.java:2078) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager.invokeBindMethod(DependencyManager.java:2061) ~[?:?]
        at org.apache.felix.scr.impl.manager.SingleComponentManager.invokeBindMethod(SingleComponentManager.java:443) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager$MultipleDynamicCustomizer.addedService(DependencyManager.java:336) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager$MultipleDynamicCustomizer.addedService(DependencyManager.java:304) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.customizerAdded(ServiceTracker.java:1232) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.customizerAdded(ServiceTracker.java:1152) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$AbstractTracked.trackAdding(ServiceTracker.java:959) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$AbstractTracked.track(ServiceTracker.java:895) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.serviceChanged(ServiceTracker.java:1184) ~[?:?]
        at org.apache.felix.scr.impl.BundleComponentActivator$ListenerInfo.serviceChanged(BundleComponentActivator.java:116) ~[?:?]
        at org.eclipse.osgi.internal.serviceregistry.FilteredServiceListener.serviceChanged(FilteredServiceListener.java:123) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.dispatchEvent(BundleContextImpl.java:961) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:234) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEventPrivileged(ServiceRegistry.java:937) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEvent(ServiceRegistry.java:874) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.register(ServiceRegistrationImpl.java:141) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.registerService(ServiceRegistry.java:262) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.registerService(BundleContextImpl.java:500) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.apache.felix.scr.impl.manager.AbstractComponentManager$3.register(AbstractComponentManager.java:929) ~[?:?]
        at org.apache.felix.scr.impl.manager.AbstractComponentManager$3.register(AbstractComponentManager.java:915) ~[?:?]
        at org.apache.felix.scr.impl.manager.RegistrationManager.changeRegistration(RegistrationManager.java:133) ~[?:?]
        at org.apache.felix.scr.impl.manager.AbstractComponentManager.activateInternal(AbstractComponentManager.java:752) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager$SingleStaticCustomizer.addedService(DependencyManager.java:1274) ~[?:?]
        at org.apache.felix.scr.impl.manager.DependencyManager$SingleStaticCustomizer.addedService(DependencyManager.java:1225) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.customizerAdded(ServiceTracker.java:1232) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.customizerAdded(ServiceTracker.java:1152) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$AbstractTracked.trackAdding(ServiceTracker.java:959) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$AbstractTracked.track(ServiceTracker.java:895) ~[?:?]
        at org.apache.felix.scr.impl.manager.ServiceTracker$Tracked.serviceChanged(ServiceTracker.java:1184) ~[?:?]
        at org.apache.felix.scr.impl.BundleComponentActivator$ListenerInfo.serviceChanged(BundleComponentActivator.java:116) ~[?:?]
        at org.eclipse.osgi.internal.serviceregistry.FilteredServiceListener.serviceChanged(FilteredServiceListener.java:123) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.dispatchEvent(BundleContextImpl.java:961) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:234) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEventPrivileged(ServiceRegistry.java:937) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEvent(ServiceRegistry.java:874) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.register(ServiceRegistrationImpl.java:141) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.registerService(ServiceRegistry.java:262) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.registerService(BundleContextImpl.java:500) ~[org.eclipse.osgi-3.18.0.jar:?]
         at org.eclipse.osgi.internal.framework.BundleContextImpl.registerService(BundleContextImpl.java:519) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.eclipse.osgi.internal.framework.BundleContextImpl.registerService(BundleContextImpl.java:1047) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.openhab.core.internal.service.WatchServiceImpl.registerWatchService(WatchServiceImpl.java:156) ~[?:?]
        at java.util.concurrent.CompletableFuture$UniRun.tryFire(Unknown Source) ~[?:?]
        at java.util.concurrent.CompletableFuture.postComplete(Unknown Source) ~[?:?]
        at java.util.concurrent.CompletableFuture$AsyncRun.run(Unknown Source) ~[?:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[?:?]
        at java.util.concurrent.FutureTask.run(Unknown Source) ~[?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[?:?]
        at java.lang.Thread.run(Unknown Source) [?:?]
Caused by: java.lang.NullPointerException: A null service reference is not allowed.
        at org.eclipse.osgi.internal.framework.BundleContextImpl.getService(BundleContextImpl.java:658) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.openhab.automation.java223.internal.Java223ScriptEngineFactory.getAdditionalBindings(Java223ScriptEngineFactory.java:285) ~[?:?]
        at org.openhab.automation.java223.internal.Java223ScriptEngineFactory.<init>(Java223ScriptEngineFactory.java:138) ~[?:?]
        ... 74 more
2025-09-03 17:00:56.504 [WARN ] [t.ScriptTransformationServiceFactory] - bundle org.openhab.core.automation.module.script:5.1.0.202509011642 (165)[org.openhab.core.automation.module.script.ScriptTransformationServiceFactory(200)] : Could not get service from ref {org.openhab.core.automation.module.script.ScriptEngineFactory, org.openhab.automation.java223.internal.Java223ScriptEngineFactory, org.openhab.core.events.EventSubscriber}={enableHelper=false, service.id=461, service.bundleid=248, service.scope=bundle, osgi.ds.satisfying.condition.target=(osgi.condition.id=true), $002.target=(watchservice.name=configWatcher), service.pid=automation.java223, component.name=org.openhab.automation.java223.internal.Java223ScriptEngineFactory, component.id=1}
2025-09-03 17:00:56.506 [WARN ] [t.ScriptTransformationServiceFactory] - bundle org.openhab.core.automation.module.script:5.1.0.202509011642 (165)[org.openhab.core.automation.module.script.ScriptTransformationServiceFactory(200)] : DependencyManager : invokeBindMethod : Service not available from service registry for ServiceReference {org.openhab.core.automation.module.script.ScriptEngineFactory, org.openhab.automation.java223.internal.Java223ScriptEngineFactory, org.openhab.core.events.EventSubscriber}={enableHelper=false, service.id=461, service.bundleid=248, service.scope=bundle, osgi.ds.satisfying.condition.target=(osgi.condition.id=true), $002.target=(watchservice.name=configWatcher), service.pid=automation.java223, component.name=org.openhab.automation.java223.internal.Java223ScriptEngineFactory, component.id=1} for reference ScriptEngineFactory
2025-09-03 17:00:56.512 [ERROR] [.internal.Java223ScriptEngineFactory] - bundle org.openhab.automation.java223:5.0.0 (248)[org.openhab.automation.java223.internal.Java223ScriptEngineFactory(1)] : Error during instantiation of the implementation object

and others. However if I stop OpenHAB and start OpenHAB again this error does not appear, so it seems it happens only when the cache is utilized (after openhab-cli clean-cache)

No, when asked by openHAB, the bundle compiles a script (a file, a script in the GUI, etc.), with all the library content included in its classloader. The resulting compilation unit is cached (whether there is something in lib or not is not relevant).
Then the following executions skip the compilation part.

Hum, not exactly, because I wasn’t aware of the script dependency tracker feature in openHAB, and so wasn’t using it… Interesting!
Had I use it, I may have not used a custom cache with custom code to invalidate it, and it paves the way for using the openHAB compiled script caching system in place of mine with caffeine. (it is several “may”. I have to look at it carefully). So, it could be a nice simplification. I will try something sooner or later.

I’m totally open to an alternate caching system (ie, PR welcome). I included caffeine because I don’t like to reinvent the wheel, and caching is often trickier than anticipated. BUT, as I said just a few lines above, the java223 caching code is now a good candidate to be removed. I will check the removal first.

Good remark, but as I already deliver a release version with enableHelper, I prefer not to touch it for the sake of continuity.

I’d rather not. I think ON is the good value because I really think most user will use it (simplicity, again). And also because I think some user (who doesn’t read the doc thoroughly) will be able to see what it can do.
But I can include an automatic deletion when user disable it (using a safe system like putting a marker at the beginning of the generated files to know what to delete)

Exactly how you are using it ! (with or without toString())
For me, it works. (I tested on openHAB 5.0.1)

Frame label="Demo2 Items" {
    Text item=Conversation_with_XXXX_Message_Received label="[JAVA(|   return input +  \" (TRANSFORM SITEMAP ADDED)\";):%s]"
}

And in a transformation profile in the GUI

And the result is

So it should be totally OK with the way you are doing it.

Caused by: java.lang.NullPointerException: A null service reference is not allowed.
        at org.eclipse.osgi.internal.framework.BundleContextImpl.getService(BundleContextImpl.java:658) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.openhab.automation.java223.internal.Java223ScriptEngineFactory.getAdditionalBindings(Java223ScriptEngineFactory.java:285) ~[?:?]

It points to a line not really related to the bundle :

    RuleManager ruleManager = bundleContext.getService(bundleContext.getServiceReference(RuleManager.class));

The context should be able to give the service. Maybe a strange initialisation exception ? Did you try another restart to see if it’s a “always” kind of error or only a contextual one ?

In OH 5.1.0 build 4815 I have /usr/share/openhab/addons/org.openhab.automation.java223-5.0.0.jar . The inline transformation fails:

Text item=x label="JAVA   [JAVA(|   return input.toString() +  \" JAVA\";):%s]"

2025-09-05 11:56:50.348 [WARN ] [ui.internal.items.ItemUIRegistryImpl] - Failed transforming the value 'BRUM' with pattern 'JAVA(|   return input.toString() +  " JAVA";):BRUM': Failed to execute script.

With OpenHAB 5.0.1 I get the same error. Can the “failed to execute script” be made somehow more verbose? I am using java “21.0.8” 2025-07-15 LTS, 64-Bit Server VM Zulu21.44+17-CA (build 21.0.8+9-LTS, mixed mode, sharing).

I get the below text after stopping openhab, doing openhab-cli clean-cache and starting openhab. However not if I just stop and start openhab, without cleaning the cache. Can this be reproduced?

Caused by: java.lang.NullPointerException: A null service reference is not allowed.
        at org.eclipse.osgi.internal.framework.BundleContextImpl.getService(BundleContextImpl.java:658) ~[org.eclipse.osgi-3.18.0.jar:?]
        at org.openhab.automation.java223.internal.Java223ScriptEngineFactory.getAdditionalBindings(Java223ScriptEngineFactory.java:285) ~[?:?]

but as I already deliver a release version with enableHelper, I prefer not to touch it for the sake of continuity.

This java223-5.0 release was made just a few days ago and probably nobody yet is using this option. So this is the right time to improve the name of the option.

Nota bene: I get the error with the transformation with “Enable Helper Library: disable”. When I enable it, the transformation works.

Here again on the wording “Enable Helper Library”- if it is a binary option, then there is no need to have ”Enable” as part of the option name. I suggest here the wording“Generate Helper Library”. Or even libraries (plural), as this puts more than one files in openhab/automation/lib/java.

I’m not sure it was exacltly your bug, but I fixed one nonetheless, so it may be OK with the last version.

I reproduced the same bug (nearly sure it is an openHAB one). A required service is not available during the bundle initialization if the cache is cleaned.
I made a workaround : waiting (20s max) until the service is ready.

And I did include this.

When I increase the logging level to TRACE under Add-On Settings → Java223 Scripting Automation, I see in openhab.log:

2025-09-13 10:22:30.882 [TRACE] [ation.java223.common.BindingInjector] - Cannot find an element with the key logger

The generated automation/lib/java/helper/generated/Items.java contains:

    protected Item getItem(String itemId) {
        if (itemRegistry != null) {
            return itemRegistry.get(itemId);
        }
        throw new Java223Exception("Items class not properly initialized. Use automatic instanciation by injection.");
    }

When will itemRegistry be null? Isn’t Java223 loaded after itemRegistry is initialized, and thus all the lib/java files are executed afterwards?

How can I write this transformation:

return java.util.regex.Pattern.matches("A.*", input) ? "TRUE" : "FALSE";

Whatever I try, with or without import java.util.regex.Pattern;, it always fails. This works transformationPattern="DSL:|if (java.util.regex.Pattern.matches(\"E.*x.*D.*\", input)) ON else OFF". The logged error is just 2025-09-13 10:59:23.983 [WARN ] [ui.internal.items.ItemUIRegistryImpl] - Failed transforming the value 'BRUM' with pattern 'JAVA(u.java):BRUM': Failed to execute script. In particular, inline fails also:

Text item=x label="M [JAVA(|return java.util.regex.Pattern.matches(\"A.*\", input) ? \"TRUE\" : \"FALSE\";):%s]"

I got the transformation running:

This transformation return input.getClass().toString(); prints class java.lang.String. This transformation return java.util.regex.Pattern.matches("A.*", input.toString()) ? "TRUE4" : "FALSE4"; prints FALSE4. But without toString() this transformation return java.util.regex.Pattern.matches("A.*", input) ? "TRUE6" : "FALSE6"; does not work, it produces the error:

2025-09-14 07:42:08.613 [WARN ] [ui.internal.items.ItemUIRegistryImpl] - Failed transforming the value ‘BRUM’ with pattern ‘JAVA(| return java.util.regex.Pattern.matches(“A.*”, input) ? “TRUE6” : “FALSE6”;):BRUM’: Failed to execute script.

when the transformation is put in a sitemap:

Text item=x label=“JT [JAVA(| return java.util.regex.Pattern.matches(\“A.*\”, input) ? \“TRUE6\” : \“FALSE6\”;):%s]”

However, if I add:

diff --git a/bundles/org.openhab.automation.java223/src/main/java/org/openhab/automation/java223/internal/Java223ScriptEngine.java b/bundles/org.openhab.automation.java223/src/main/java/org/openhab/automation/java223/internal/Java223ScriptEngine.java
index af1dd35c92..3fdd60e946 100644
--- a/bundles/org.openhab.automation.java223/src/main/java/org/openhab/automation/java223/internal/Java223ScriptEngine.java
+++ b/bundles/org.openhab.automation.java223/src/main/java/org/openhab/automation/java223/internal/Java223ScriptEngine.java
@@ -106,6 +106,7 @@ public class Java223ScriptEngine extends JavaScriptEngine implements Invocable {
             if (!task.call()) {
                 String message = diagnostics.getDiagnostics().stream().map(Object::toString)
                         .collect(Collectors.joining("\n"));
+                logger.debug("Error " + message);
                 throw new ScriptException(message);
             }

then openhab.log contains:

2025-09-14 07:36:27.915 [DEBUG] [java223.internal.Java223ScriptEngine] - Error /WrappedJavaScript.java:65: error: incompatible types: java.lang.Object cannot be converted to java.lang.CharSequence
return java.util.regex.Pattern.matches("A.*", input) ? "TRUE6" : "FALSE6";
                                              ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output

The description - Object cannot be converted to CharSequence - is far more useful than Failed transforming the value 'BRUM' with pattern 'JAVA(| return java.util.regex.Pattern.matches("A.*", input) ? "TRUE2" : "FALSE2";):BRUM': Failed to execute script. So the problem is that the message from throw new ScriptException(message) is not logged.