In this article
Intro
Updated 31 Oct 2026: this guide has been re-tested end to end on VCF Operations Orchestrator 9.1.1 (build 25643527). The first version of this post was written on 9.0.2 and mixed my test results with what the Rhino engine itself is documented to support, so the summary table and the individual sections did not always agree. Every result below now comes from running the code on a real 9.1.1 appliance.
Orchestrator runs all of our JavaScript on the Rhino engine. Orchestrator 8.x ships Rhino 1.7R4, which means plain ES5.1. Orchestrator 9.x moved to a newer Rhino, and 9.1 moved again to an even newer one, so a lot of syntax that failed on 9.0.x (destructuring, default parameters, optional chaining, nullish coalescing and more) now just works on 9.1.1.
There is still a catch: Orchestrator does not expose everything modern JavaScript offers. Classes, async/await, spread in function calls and a few other things still fail, and some features work with surprising edge cases. Those edge cases are what will actually bite you in a workflow, so they get their own section right below.
How these results were produced
- Every feature was written directly into an Orchestrator action on 9.1.1 and executed, exactly as you would use it in a workflow.
- The same features were also checked with a one-shot test script (included at the end of this post). It runs each test through
evalinsidetry/catch, so one unsupported feature doesn’t stop the run. Rhino compileseval‘d code with the same settings as the calling script, and both methods gave identical results. - The output comments in every code block are the real values returned on 9.1.1.
📸 Note on screenshots: the screenshots from the earlier version were taken on 9.0.2 and have been removed. 9.1.1 screenshots will be added section by section as I capture them.
Watch out for these on 9.1.1
- Spread works in arrays, not in function calls.
[...a, ...b]works, butMath.max(...arr)is a syntax error. UseMath.max.apply(null, arr). - Arrow functions can’t take rest parameters.
(...args) => args.lengthfails to compile.function (...args) {}works. for (const x of list)doesn’t compile. Useletorvarinfor...of.letin a loop does not create a new binding per iteration. Closures created in afor (let i ...)loop all see the last value (3,3,3 instead of 0,1,2). Declare aletinside the loop body or useforEach.- Reassigning a
constis silently ignored, with no error. The value just doesn’t change. - Promise callbacks run only after your script returns.
.then()callbacks do execute, but after the action or script task has already finished, so you can’t use their results in the same script. - No
console. Keep usingSystem.log(). - No
Intl. Also,(1234.5).toLocaleString("en-US")throwsRangeError: illegal radix 0; call it without arguments or format manually. - Java collections are converted.
new java.util.ArrayList()becomes a JavaScript array (no.add(), usepush). Ajava.util.HashMapsupports property access and.get(), but not.size(). - Strict mode is partial. Undeclared assignments and writes to frozen objects throw, but
thisinside a plain function call is still the global scope. - None of this runs on 8.x. If a package will also be imported on Orchestrator 8.x (or onto a 9.0.x appliance you haven’t tested), keep it ES5.1.
Feature support at a glance
The 8.x column is carried over from my original testing on Orchestrator 8.x. The 9.1.1 column is fully re-tested. A dash means the feature wasn’t part of the 8.x test.
| Feature | Orchestrator 8.x | Orchestrator 9.1.1 | Notes on 9.1.1 |
|---|---|---|---|
| ECMAScript level | ES5.1 | ES5.1 + most of ES2015–ES2023 | |
| Rhino engine | 1.7R4 | Newer Rhino (upgraded in 9.1) | 9.0.x shipped 1.7.15 |
| Arrow functions | ❌ No | ✅ Yes | No rest parameters in arrows |
| Classes | ❌ No | ❌ No | class is a reserved word |
| Template literals (incl. tagged) | ❌ No | ✅ Yes | |
| String.raw | ❌ No | ✅ Yes | |
| Destructuring (array, object, nested, defaults, parameters) | ⚠️ Partial (JS 1.7) | ✅ Yes | |
| Default parameters | ❌ No | ✅ Yes | |
| Rest parameters | ❌ No | ✅ Yes | Regular functions only |
| Spread in array literals | ❌ No | ✅ Yes | |
| Spread in function calls | ❌ No | ❌ No | Use fn.apply(null, arr) |
Object spread {...o} | ❌ No | ✅ Yes | |
| Object rest in destructuring | ❌ No | ❌ No | Explicit SyntaxError |
| let / const | ⚠️ Basic | ⚠️ Partial | No per-iteration loop binding; const reassignment ignored |
| for…of | ❌ No | ✅ Yes | Not with const |
| Map / Set / WeakMap / WeakSet | ❌ No | ✅ Yes | |
| Promises (all, allSettled, finally) | ❌ No | ⚠️ Partial | Callbacks run after the script returns |
| Generators | ✅ Yes (JS 1.7 style) | ✅ Yes | ES6 style, incl. yield* |
| Symbols, Symbol.iterator, custom iterables | ❌ No | ✅ Yes | |
| Proxy | ❌ No | ✅ Yes | |
| Reflect | ❌ No | ✅ Yes | |
| Symbol.species | ❌ No | ⚠️ Partial | Exists, but no classes to use it with |
| super | ❌ No | ⚠️ Partial | In object-literal methods only |
| Object literal shorthand, computed keys, methods | – | ✅ Yes | |
Exponentiation **, **= | ❌ No | ✅ Yes | |
| Array.prototype.includes | ❌ No | ✅ Yes | |
| async / await | ❌ No | ❌ No | |
| Object.values / entries / fromEntries | ❌ No | ✅ Yes | |
| Object.getOwnPropertyDescriptors | ❌ No | ✅ Yes | |
| String padStart / padEnd | ❌ No | ✅ Yes | |
| Async iteration (for await) | ❌ No | ❌ No | |
| Array flat / flatMap | ❌ No | ✅ Yes | |
| Optional catch binding | ❌ No | ✅ Yes | |
| String trimStart / trimEnd | ❌ No | ✅ Yes | |
| JSON superset | ❌ No | ✅ Yes | |
| BigInt | ❌ No | ✅ Yes | |
| globalThis | ❌ No | ✅ Yes | |
Optional chaining ?. | ❌ No | ✅ Yes | |
Nullish coalescing ?? | ❌ No | ✅ Yes | |
Logical assignment ||= &&= ??= | ❌ No | ✅ Yes | |
| Numeric separators | ❌ No | ✅ Yes | |
| String.replaceAll | ❌ No | ✅ Yes | |
| Array.at | ❌ No | ✅ Yes | |
| Object.hasOwn | ❌ No | ✅ Yes | |
| Array.findLast | – | ✅ Yes | |
| Top-level await | ❌ No | ❌ No | |
Regex sticky flag y | ❌ No | ✅ Yes | |
Regex unicode flag u (incl. \u{...}, \p{...}) | ❌ No | ✅ Yes | |
Regex named groups, s flag, lookbehind, matchAll | – | ✅ Yes | |
| Number.EPSILON, isInteger, MAX_SAFE_INTEGER | ❌ No | ✅ Yes | |
| Array comprehensions (JS 1.7) | ✅ Yes | ✅ Yes | |
| Expression closures (JS 1.7) | ⚠️ Partial | ✅ Yes | |
| E4X (XML literals) | ✅ Yes | ✅ Yes | |
| Strict mode | ⚠️ Partial | ⚠️ Partial | this in plain calls is still global |
| console | ❌ No | ❌ No | Use System.log |
| Error.stack | ❌ No | ✅ Yes | Frames are module/action:line (function) |
| Intl | ❌ No | ❌ No | toLocaleString("en-US") throws |
| Java interop | ✅ Yes | ⚠️ Partial | Java collections are converted to JS types |
Arrow functions ✅
var add = (a, b) => a + b;System.log(add(5, 3)); // 8var numbers = [1, 2, 3, 4, 5];System.log(numbers.map(n => n * 2)); // 2,4,6,8,10System.log(numbers.filter(n => n % 2 === 0)); // 2,4var greet = name => { var message = "Hello, " + name; return message;};System.log(greet("World")); // Hello, World// Lexical "this" works as expectedvar counter = { count: 0, start: function () { [1, 2, 3].forEach(() => { this.count++; }); return this.count; }};System.log(counter.start()); // 3// NOT supported: rest parameter in an arrow function// var count = (...args) => args.length; // SyntaxError
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Classes ❌
class still fails to compile on 9.1.1 with SyntaxError: identifier is a reserved word: class. Because of that, extends and class-style super are not usable either. Stick to constructor functions and prototypes, or object literals with Object.setPrototypeOf (see the super section).
// Fails on 9.1.1:// class Parent { say() { return "Hello"; } }// Works:function Parent(name) { this.name = name; }Parent.prototype.say = function () { return "Hello " + this.name; };System.log(new Parent("vRO").say()); // Hello vRO
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Template literals and String.raw ✅
var name = "World";System.log(`Hello, ${name}!`); // Hello, World!var a = 10, b = 20;System.log(`The sum of ${a} and ${b} is ${a + b}`); // The sum of 10 and 20 is 30// Multi-line stringsvar multiLine = `line 1line 2`;System.log(multiLine.split("\n").length); // 2// Tagged templatesfunction tag(strings, v1) { return strings[0] + "[" + v1 + "]" + strings[1]; }System.log(tag`value ${42} here`); // value [42] here// String.raw keeps backslashes as-isSystem.log(String.raw`C:\Users\Admin\Documents`); // C:\Users\Admin\DocumentsSystem.log(String.raw`Line 1\nLine 2`); // Line 1\nLine 2
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
BigInt ✅
var bigNum = 9007199254740991n;var anotherBig = BigInt("9007199254740992");System.log(bigNum + 1n); // 9007199254740992System.log(anotherBig * 2n); // 18014398509481984System.log(typeof bigNum); // bigintSystem.log(2n ** 64n); // 18446744073709551616// Mixing BigInt and Number throws// var bad = 10n + 5; // TypeError: Cannot convert BigInt to a numbervar good = 10n + BigInt(5);System.log(good); // 15// Store BigInts as strings in Properties, attributes and outputsvar props = new Properties();props.put("big", (2n ** 64n).toString());
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Destructuring ✅ (object rest ❌)
var person = { name: "John", age: 30, city: "NYC" };var { name, age } = person;System.log(name + " " + age); // John 30var { country = "USA" } = person; // default valueSystem.log(country); // USAvar { name: fullName } = person; // renamingSystem.log(fullName); // Johnvar user = { id: 1, details: { name: "Alice", email: "alice@example.com" } };var { details: { name: userName, email } } = user; // nestedSystem.log(userName + " " + email); // Alice alice@example.comvar [first, , third = 9, fourth = 4] = [1, 2, 3]; // arrays, holes, defaultsSystem.log(first + " " + third + " " + fourth); // 1 3 4var x = 1, y = 2;[x, y] = [y, x]; // swapSystem.log(x + " " + y); // 2 1function show({ host, port = 443 }) { return host + ":" + port; } // parametersSystem.log(show({ host: "auto.vmw.lab" })); // auto.vmw.lab:443// NOT supported: object rest// var { a, ...rest } = { a: 1, b: 2 };// SyntaxError: object rest properties in destructuring are not supported
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Default parameters ✅
function greet(name = "Guest", greeting = "Hello") { return greeting + ", " + name + "!";}System.log(greet()); // Hello, Guest!System.log(greet("Alice")); // Hello, Alice!System.log(greet("Bob", "Hi")); // Hi, Bob!System.log(greet(undefined, "Hey")); // Hey, Guest! (undefined uses the default)System.log(greet(null)); // Hello, null! (null does not)function createArray(length = 10, fill = 0) { return Array(length).fill(fill); }System.log(JSON.stringify(createArray(5, 1))); // [1,1,1,1,1]System.log(JSON.stringify(createArray(3))); // [0,0,0]function total(a, b = a * 2) { return a + b; } // defaults can use earlier paramsSystem.log(total(3)); // 9
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Rest parameters ✅
function sum(...numbers) { return numbers.reduce(function (acc, num) { return acc + num; }, 0);}System.log(sum(1, 2, 3, 4, 5)); // 15function greetAll(greeting, ...names) { return names.map(name => greeting + ", " + name).join(" | ");}System.log(greetAll("Hello", "Alice", "Bob")); // Hello, Alice | Hello, BobSystem.log(Array.isArray((function (...r) { return r; })(1))); // true (a real array)// NOT supported in arrow functions:// var count = (...args) => args.length; // SyntaxError
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Spread ⚠️ (arrays and objects yes, function calls no)
var arr1 = [1, 2, 3];var arr2 = [4, 5, 6];System.log(JSON.stringify([...arr1, ...arr2])); // [1,2,3,4,5,6]var original = [1, 2, 3];var copy = [...original];copy.push(4);System.log(JSON.stringify(original) + " " + JSON.stringify(copy)); // [1,2,3] [1,2,3,4]System.log(JSON.stringify([..."abc"])); // ["a","b","c"]System.log(JSON.stringify([...new Set([1, 1, 2, 3])])); // [1,2,3] (de-duplicate)// Object spread worksvar o = { ...{ a: 1 }, b: 2 };System.log(o.a + " " + o.b); // 1 2// NOT supported: spread in a function call// Math.max(...arr1); // SyntaxErrorSystem.log(Math.max.apply(null, arr1)); // 3 - use apply instead
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
let and const ⚠️
Block scoping works, but two behaviours differ from modern JavaScript and are easy to miss.
const PI = 3.14159;System.log(PI); // 3.14159function testScope() { let x = 10; if (true) { let x = 20; System.log(x); // 20 } System.log(x); // 10 for (let i = 0; i < 3; i++) { } System.log(typeof i); // undefined (i stays inside the loop)}testScope();const config = { debug: true };config.debug = false; // OK - the object itself is mutableconfig.timeout = 5000;System.log(JSON.stringify(config)); // {"debug":false,"timeout":5000}// Gotcha 1: reassigning a const is silently ignored (no error)const K = 1;K = 2;System.log(K); // 1// Gotcha 2: no new binding per loop iterationvar fns = [];for (let j = 0; j < 3; j++) { fns.push(function () { return j; }); }System.log(fns.map(function (f) { return f(); }).join(",")); // 3,3,3 (not 0,1,2)// Fix: declare a let inside the loop body (or use forEach)var fixed = [];for (let j = 0; j < 3; j++) { let k = j; fixed.push(function () { return k; }); }System.log(fixed.map(function (f) { return f(); }).join(",")); // 0,1,2
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
for…of, Map and Set ✅
for (var fruit of ["apple", "banana"]) { System.log(fruit); // apple, banana}for (var ch of "Hello") { System.log(ch); // H, e, l, l, o}var map = new Map();map.set("a", 1);map.set("b", 2);for (var [key, value] of map) { System.log(key + " = " + value); // a = 1, b = 2}System.log(map.size + " " + map.has("a")); // 2 trueSystem.log(Array.from(map.keys()).join(",")); // a,bvar set = new Set([1, 2, 2, 3]);for (var num of set) { System.log(num); // 1, 2, 3}var wm = new WeakMap();var k = {};wm.set(k, "v");System.log(wm.get(k)); // v// NOT supported: const in for...of// for (const item of [1, 2]) { } // SyntaxError - use let or var// Note: let in for...of shares one binding too (closures see the last value)
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Promises ⚠️
Promise, Promise.all, Promise.allSettled and finally all exist, and the executor function runs immediately. The catch is when the callbacks run: .then() callbacks are queued and only execute after your script or action has returned. I confirmed this by writing a file from inside a .then() and reading it back in a second run: the callback did run, but too late for the script that created it to use its result.
var results = [];new Promise(function (resolve) { System.log("executor runs immediately"); // logged resolve(1);});Promise.resolve("value").then(function (v) { results.push(v); });System.log(results.length); // 0 - the callback hasn't run yet// What this means in practice:// - Don't use Promises to compute values you need later in the same script.// - Orchestrator APIs are synchronous anyway; keep the code synchronous.
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Generators ✅
function* fibonacci() { var a = 0, b = 1; while (true) { yield a; var t = a; a = b; b = t + b; }}var fib = fibonacci();var first = [];for (var i = 0; i < 6; i++) { first.push(fib.next().value); }System.log(first.join(",")); // 0,1,1,2,3,5function* range(n) { for (var i = 0; i < n; i++) { yield i; } }for (var v of range(4)) { System.log(v); } // 0, 1, 2, 3System.log(JSON.stringify([...range(3)])); // [0,1,2]function* inner() { yield 1; yield 2; }function* outer() { yield 0; yield* inner(); }System.log(JSON.stringify([...outer()])); // [0,1,2]
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Symbols and iterators ✅
var sym1 = Symbol("description");var sym2 = Symbol("description");System.log(sym1 === sym2); // false - every symbol is uniqueSystem.log(sym1.toString()); // Symbol(description)var obj = { visible: 1 };var privateKey = Symbol("private");obj[privateKey] = "secret value";System.log(obj[privateKey]); // secret valueSystem.log(JSON.stringify(Object.keys(obj))); // ["visible"] - symbols are hiddenSystem.log(JSON.stringify(obj)); // {"visible":1}System.log(Symbol.for("app") === Symbol.for("app")); // truevar it = [1, 2, 3][Symbol.iterator]();System.log(JSON.stringify(it.next())); // {"value":1,"done":false}// Make your own iterablevar range = { from: 1, to: 3 };range[Symbol.iterator] = function () { var c = this.from, t = this.to; return { next: function () { return c <= t ? { value: c++, done: false } : { value: undefined, done: true }; } };};for (var x of range) { System.log(x); } // 1, 2, 3
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Proxy and Reflect ✅
var target = { name: "Alice", age: 30 };var proxy = new Proxy(target, { get: function (obj, prop) { System.log("get " + String(prop)); return prop in obj ? obj[prop] : "Property not found"; }, set: function (obj, prop, value) { System.log("set " + prop + " = " + value); obj[prop] = value; return true; }});System.log(proxy.name); // get name -> AliceSystem.log(proxy.city); // get city -> Property not foundproxy.age = 31; // set age = 31System.log(target.age); // 31System.log(Reflect.has(target, "name")); // trueSystem.log(Reflect.get(target, "age")); // 31Reflect.set(target, "city", "NYC");System.log(JSON.stringify(Reflect.ownKeys(target))); // ["name","age","city"]// Practical use: validate input objectsvar vmSpec = new Proxy({}, { set: function (o, k, v) { if (typeof v !== "number") { throw new TypeError(k + " must be a number"); } o[k] = v; return true; }});try { vmSpec.cpu = "two"; } catch (e) { System.log(e.message); } // cpu must be a number
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
super, Symbol.species and object literal upgrades ⚠️
Without classes, super is only usable inside object-literal methods (shorthand syntax) whose prototype you set with Object.setPrototypeOf. Symbol.species exists, but there is no class to override it on.
var parent = { say: function () { return "Hello"; } };var kid = { say() { return super.say() + " World"; } };Object.setPrototypeOf(kid, parent);System.log(kid.say()); // Hello WorldSystem.log(typeof Symbol.species); // symbolSystem.log(Array[Symbol.species] === Array); // true// Shorthand properties, computed keys, method shorthand, gettersvar host = "vra";var o = { host, ["key_" + host]: 1, greet() { return "hi " + this.host; }, get upper() { return this.host.toUpperCase(); }};System.log(o.host + " " + o.key_vra + " " + o.greet() + " " + o.upper); // vra 1 hi vra VRA
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
ES2016–ES2023 built-ins ✅
All of these work on 9.1.1, including the ones the earlier version of this post listed as unsupported (at(), replaceAll()).
System.log(2 ** 3); // 8var n = 2; n **= 3;System.log(n); // 8System.log(10 ** -1); // 0.1System.log([1, 2, 3].includes(2)); // trueSystem.log([NaN].includes(NaN) + " " + [NaN].indexOf(NaN)); // true -1System.log("abc".startsWith("a") + " " + "abc".endsWith("c") + " " + "ab".repeat(2)); // true true ababvar person = { name: "Alice", age: 30, city: "NYC" };System.log(JSON.stringify(Object.values(person))); // ["Alice",30,"NYC"]System.log(JSON.stringify(Object.entries(person))); // [["name","Alice"],["age",30],["city","NYC"]]for (var [key, value] of Object.entries(person)) { System.log(key + ": " + value); // name: Alice ...}System.log(JSON.stringify(Object.fromEntries([["a", 1], ["b", 2]]))); // {"a":1,"b":2}System.log(JSON.stringify(Object.fromEntries(new Map([["x", 9]])))); // {"x":9}var source = { get foo() { return "bar"; } };var clone = Object.create(Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source));System.log(clone.foo); // bar (getter copied, not its value)System.log("42".padStart(5, "0") + " " + "42".padEnd(5, "-")); // 00042 42---System.log("[" + " hello ".trimStart() + "][" + " hello ".trimEnd() + "]"); // [hello ][ hello]var nested = [1, 2, [3, 4, [5, 6]]];System.log(JSON.stringify(nested.flat())); // [1,2,3,4,[5,6]]System.log(JSON.stringify(nested.flat(Infinity))); // [1,2,3,4,5,6]System.log(JSON.stringify(["Hello World", "Foo Bar"].flatMap(s => s.split(" ")))); // ["Hello","World","Foo","Bar"]System.log([10, 20, 30].at(-1) + " " + "abc".at(-1)); // 30 cSystem.log("a,b,c".replaceAll(",", "|")); // a|b|cSystem.log(Object.hasOwn({ a: 1 }, "a") + " " + Object.hasOwn({}, "toString")); // true falsevar nullProto = Object.create(null);nullProto.prop = "value";System.log(Object.hasOwn(nullProto, "prop")); // true (hasOwnProperty would fail here)System.log([5, 12, 8].find(x => x > 10) + " " + [5, 12, 8].findIndex(x => x > 10)); // 12 1System.log([1, 2, 3].findLast(x => x < 3)); // 2System.log(JSON.stringify(Array.of(7, 8)) + " " + JSON.stringify(Array.from("hi"))); // [7,8] ["h","i"]
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Optional catch binding and numeric separators ✅
try { throw new Error("Something went wrong");} catch { System.log("An error occurred"); // no (e) needed}var big = 1_000_000;System.log(big); // 1000000
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Optional chaining, nullish coalescing and logical assignment ✅
These were listed as unsupported in the earlier version of this post. On 9.1.1 they all work, and they are probably the most useful upgrade for day-to-day workflow code that reads optional inputs and API responses.
var vm = { spec: { cpu: 2 }, greet() { return "Hello!"; } };System.log(vm.spec?.cpu); // 2System.log(vm.disk?.size); // undefined (no TypeError)System.log(vm.spec?.["cpu"]); // 2System.log(vm.greet?.()); // Hello!System.log(vm.sayBye?.()); // undefinedvar list = null;System.log(list?.[0]); // undefinedvar val = null;System.log(val ?? "default"); // defaultSystem.log(0 ?? 42); // 0 (?? only replaces null/undefined)System.log(0 || 42); // 42 (|| replaces any falsy value)var a = null; a ||= 5;var b = 1; b &&= 2;var c; c ??= "set";System.log(a + " " + b + " " + c); // 5 2 set
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Regular expressions ✅
var sticky = /foo/y;sticky.lastIndex = 3;System.log(sticky.test("barfoo")); // true (matches only at lastIndex)System.log(/foo/y.test("barfoo")); // false (lastIndex 0)System.log(/\u{1F600}/u.test("😀")); // trueSystem.log(/^.$/u.test("😀") + " " + /^.$/.test("😀")); // true falseSystem.log(/\p{Script=Greek}/u.test("α")); // truevar m = "2026-10".match(/(?<year>\d{4})-(?<month>\d{2})/);System.log(m.groups.year + " " + m.groups.month); // 2026 10System.log(/a.b/s.test("a\nb")); // true (dotAll)System.log(/(?<=\$)\d+/.exec("cost $42")[0]); // 42 (lookbehind)System.log([..."a1b2".matchAll(/\d/g)].length); // 2
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
JSON superset and Number additions ✅
// U+2028 / U+2029 are now allowed inside string literalsSystem.log(JSON.parse('"\u2028"').length); // 1System.log(Number.EPSILON); // 2.220446049250313e-16System.log(0.1 + 0.2 === 0.3); // falseSystem.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // trueSystem.log(Number.isInteger(5)); // trueSystem.log(Number.isSafeInteger(2 ** 53)); // falseSystem.log(Number.MAX_SAFE_INTEGER); // 9007199254740991System.log(Math.trunc(4.7) + " " + Math.sign(-3)); // 4 -1
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
async/await, async iteration and top-level await ❌
None of these compile on 9.1.1:
async function/await:SyntaxError: missing ; before statementfor await (... of ...):SyntaxError: missing ; before statement- Top-level
await:SyntaxError: identifier is a reserved word: await
Since Orchestrator’s own APIs are synchronous, and Promise callbacks only run after the script returns (see Promises), there is little to gain from asynchronous code inside a script task anyway.
Legacy Rhino (JS 1.7) features ✅
The old Mozilla-only extensions still work on 9.1.1. They are handy to recognise in legacy code, but avoid them in new code: they are not standard JavaScript and tooling won’t understand them.
var squares = [x * x for each (x in [1, 2, 3])]; // array comprehensionSystem.log(squares.join(",")); // 1,4,9var double = function (x) x * 2; // expression closureSystem.log(double(4)); // 8var xml = <vm><name>web01</name><cpu>2</cpu></vm>; // E4XSystem.log(xml.name + " cpu " + xml.cpu); // web01 cpu 2
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Strict mode ⚠️
(function () { "use strict"; try { undeclaredVar = 1; } catch (e) { System.log(e.name); } // ReferenceError var frozen = Object.freeze({ a: 1 }); try { frozen.a = 2; } catch (e) { System.log(e.name); } // TypeError})();// But: "this" in a plain function call is NOT undefinedvar self = (function () { "use strict"; return String(this); })();System.log(self); // [object Main] - still the global scope
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
globalThis, Error.stack, console and Intl
globalThis ✅
System.log(typeof globalThis); // objectglobalThis.myGlobalVar = "Hello from global";System.log(myGlobalVar); // Hello from global
Error.stack ✅
function level3() { throw new Error("Something went wrong"); }function level2() { level3(); }function level1() { level2(); }try { level1();} catch (e) { System.log(e.message); // Something went wrong System.log(e.stack); // at com.example/myAction:1 (level3) // at com.example/myAction:2 (level2) // at com.example/myAction:3 (level1) // ...}
The frames show the module/action name, the line number and the function name, which is genuinely useful when logging errors from shared actions.
console ❌ and Intl ❌
System.log(typeof console); // undefined - use System.log / System.warn / System.errorSystem.log(typeof Intl); // undefined// Careful with locale formatting:// (1234567.891).toLocaleString("en-US"); // RangeError: illegal radix 0System.log((1234567.891).toLocaleString()); // 1234567.891 (no grouping)System.log((1234567.891).toFixed(2)); // 1234567.89System.log(new Date(Date.UTC(2025, 9, 15)).toLocaleDateString("en-US")); // 10/15/25System.log(new Date(Date.UTC(2025, 9, 15)).toISOString()); // 2025-10-15T00:00:00.000Z
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Java interoperability ⚠️
Orchestrator converts Java collections into its own JavaScript-friendly types, so they don’t behave like the Java classes you asked for.
var map = new java.util.HashMap();map.put("key1", "value1");map.put("key2", "value2");System.log(map.key1); // value1 (property access works)System.log(map["key2"]); // value2System.log(map.get("key1")); // value1// map.size(); // TypeError: Cannot find function size.var list = new java.util.ArrayList();System.log(Array.isArray(list)); // true - it's a JavaScript array now// list.add("apple"); // TypeError: Cannot find function add.list.push("banana");System.log(list.length); // 1delete list[0]; // leaves a hole, doesn't remove the itemSystem.log(JSON.stringify(list)); // [null]
Use native JavaScript arrays, objects, Map and Set, or Orchestrator’s Properties, instead of Java collections.
📸 Screenshot of this running on 9.1.1 to be added. The code and output comments above were verified on a 9.1.1 appliance.
Test your own Orchestrator in one go
Want to check a different version, for example a customer’s 9.0.x appliance or an 8.x instance? Paste this into a scratch action (return type Properties) or a script task and run it. The script itself is plain ES5.1, so it runs everywhere. Each feature is tested in isolation, so one unsupported feature doesn’t stop the run. Anything that behaves differently from 9.1.1 is logged as a warning, and the summary at the end lists all differences.
// VCF Orchestrator - modern JavaScript feature probe// Source: cloudblogger.co.in (Rhino Engine guide). Plain ES5.1, so it runs on 8.x and 9.x.// Each test runs in its own function via eval: an unsupported syntax is caught as a// SyntaxError and the run continues. Rhino compiles eval'd code with the same settings// as the calling script, so results match writing the code directly.// "expected" = result observed on Orchestrator 9.1.1 (build 25643527).var tests = [ // [group, feature, expected on 9.1.1, test body - returns true when the feature works] ["ES2015", "let / const block scope", "yes", "let a = 1; { let a = 2; } const b = 3; return a === 1 && b === 3;"], ["ES2015", "let: new binding per for-loop iteration", "no", "var f = []; for (let i = 0; i < 2; i++) { f.push(function () { return i; }); } return f[0]() === 0;"], ["ES2015", "Arrow functions", "yes", "return [1, 2].map(x => x * 2)[1] === 4;"], ["ES2015", "Arrow function with rest parameter", "no", "var f = (...a) => a.length; return f(1, 2) === 2;"], ["ES2015", "Classes", "no", "class A { m() { return 1; } } return new A().m() === 1;"], ["ES2015", "Template literals", "yes", "var n = 3; return `n is ${n}` === 'n is 3';"], ["ES2015", "String.raw", "yes", "return String.raw`a\\nb`.length === 4;"], ["ES2015", "Destructuring (array)", "yes", "var [a, b] = [1, 2]; return a + b === 3;"], ["ES2015", "Destructuring (object)", "yes", "var { p, q: r = 2 } = { p: 1 }; return p === 1 && r === 2;"], ["ES2015", "Default parameters", "yes", "function f(x = 5) { return x; } return f() === 5;"], ["ES2015", "Rest parameters", "yes", "function f(a, ...r) { return r.length; } return f(1, 2, 3) === 2;"], ["ES2015", "Spread (array literal)", "yes", "var a = [1, 2]; return [...a, 3].length === 3;"], ["ES2015", "Spread (function call)", "no", "return Math.max(...[1, 5, 3]) === 5;"], ["ES2015", "for...of", "yes", "var s = 0; for (var v of [1, 2, 3]) { s += v; } return s === 6;"], ["ES2015", "for (const x of ...)", "no", "var s = 0; for (const v of [1, 2]) { s += v; } return s === 3;"], ["ES2015", "Map / Set", "yes", "var m = new Map([['a', 1]]); var s = new Set([1, 1, 2]); return m.get('a') === 1 && s.size === 2;"], ["ES2015", "Promise (constructor + then)", "yes", "var ran = false; var p = new Promise(function (res) { ran = true; res(1); }); return ran && typeof p.then === 'function';"], ["ES2015", "Promise callback runs before script returns", "no", "var ran = false; Promise.resolve(1).then(function () { ran = true; }); return ran;"], ["ES2015", "Generators (function*)", "yes", "function* g() { yield 1; yield 2; } var it = g(); it.next(); return it.next().value === 2;"], ["ES2015", "Symbols", "yes", "var s1 = Symbol('d'); var o = {}; o[s1] = 1; return s1 !== Symbol('d') && o[s1] === 1 && Object.keys(o).length === 0;"], ["ES2015", "Iterators (Symbol.iterator)", "yes", "var it = [1, 2][Symbol.iterator](); return it.next().value === 1;"], ["ES2015", "Proxy", "yes", "var px = new Proxy({}, { get: function (t, k) { return k + '!'; } }); return px.foo === 'foo!';"], ["ES2015", "Reflect API", "yes", "var o = {}; Reflect.set(o, 'x', 42); return o.x === 42 && Reflect.has(o, 'x');"], ["ES2015", "Symbol.species (exists)", "yes", "return typeof Symbol.species === 'symbol';"], ["ES2015", "super (in object method)", "yes", "var p = { hi: function () { return 'h'; } }; var c = { hi() { return super.hi() + 'i'; } }; Object.setPrototypeOf(c, p); return c.hi() === 'hi';"], ["ES2015", "Regex sticky flag (y)", "yes", "var r = /b/y; r.lastIndex = 1; return r.test('ab');"], ["ES2015", "Regex unicode flag (u)", "yes", "return /^.$/u.test('\\uD83D\\uDE00');"], ["ES2015", "Number.EPSILON", "yes", "return Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;"], ["ES2016", "Exponentiation (**, **=)", "yes", "var n = 2; n **= 3; return 2 ** 3 === 8 && n === 8;"], ["ES2016", "Array.prototype.includes", "yes", "return [1, 2, 3].includes(2);"], ["ES2017", "Object.values / entries", "yes", "var o = { a: 1, b: 2 }; return Object.values(o)[1] === 2 && Object.entries(o)[0][0] === 'a';"], ["ES2017", "Object.getOwnPropertyDescriptors", "yes", "var d = Object.getOwnPropertyDescriptors({ a: 1 }); return d.a.value === 1 && d.a.writable === true;"], ["ES2017", "String padStart / padEnd", "yes", "return '42'.padStart(5, '0') === '00042' && '42'.padEnd(5, '-') === '42---';"], ["ES2017", "async / await", "no", "var f = async function () { return await 1; }; return typeof f === 'function';"], ["ES2018", "Object spread properties", "yes", "var o = { ...{ a: 1 }, b: 2 }; return o.a === 1 && o.b === 2;"], ["ES2018", "Object rest properties", "no", "var { a, ...r } = { a: 1, b: 2 }; return a === 1 && r.b === 2;"], ["ES2018", "Async iteration (for await)", "no", "var f = async function () { for await (var v of []) {} }; return true;"], ["ES2018", "Promise.prototype.finally", "yes", "return typeof Promise.resolve(1).finally === 'function';"], ["ES2018", "Regex named groups / s flag / lookbehind", "yes", "return '2026-10'.match(/(?<y>\\d{4})-/).groups.y === '2026' && /a.b/s.test('a\\nb') && /(?<=\\$)\\d+/.exec('$42')[0] === '42';"], ["ES2019", "Array flat / flatMap", "yes", "return [1, [2, [3]]].flat(2).length === 3 && [1, 2].flatMap(function (x) { return [x, x]; }).length === 4;"], ["ES2019", "Object.fromEntries", "yes", "return Object.fromEntries([['a', 1]]).a === 1;"], ["ES2019", "Optional catch binding", "yes", "try { throw 1; } catch { return true; } return false;"], ["ES2019", "String trimStart / trimEnd", "yes", "return ' x '.trimStart() === 'x ' && ' x '.trimEnd() === ' x';"], ["ES2019", "JSON superset (raw U+2028 in literal)", "yes", "return '" + String.fromCharCode(0x2028) + "'.length === 1;"], ["ES2020", "BigInt", "yes", "return (9007199254740991n + 1n).toString() === '9007199254740992';"], ["ES2020", "Promise.allSettled", "yes", "return typeof Promise.allSettled === 'function';"], ["ES2020", "globalThis", "yes", "return typeof globalThis === 'object';"], ["ES2020", "Optional chaining (?.)", "yes", "var o = {}; return o.a?.b === undefined;"], ["ES2020", "Nullish coalescing (??)", "yes", "return (null ?? 'd') === 'd' && (0 ?? 1) === 0;"], ["ES2021", "Logical assignment (||= &&= ??=)", "yes", "var a = null; a ||= 5; var b = 1; b &&= 2; var c; c ??= 3; return a === 5 && b === 2 && c === 3;"], ["ES2021", "Numeric separators", "yes", "return 1_000_000 === 1000000;"], ["ES2021", "String.prototype.replaceAll", "yes", "return 'a,b,c'.replaceAll(',', '|') === 'a|b|c';"], ["ES2022", "Array.prototype.at", "yes", "return [10, 20, 30].at(-1) === 30;"], ["ES2022", "Object.hasOwn", "yes", "return Object.hasOwn({ a: 1 }, 'a') && !Object.hasOwn({}, 'toString');"], ["ES2022", "Top-level await", "no", "return eval('await null') === null;"], ["ES2023", "Array.prototype.findLast", "yes", "return [1, 2, 3].findLast(function (n) { return n < 3; }) === 2;"], ["JS 1.7", "Array comprehensions", "yes", "var a = [x * 2 for each (x in [1, 2, 3])]; return a[2] === 6;"], ["JS 1.7", "Expression closures", "yes", "var f = function (x) x * 2; return f(3) === 6;"], ["JS 1.7", "E4X (XML literals)", "yes", "var x = <a><b>1</b></a>; return x.b.toString() === '1';"], ["Platform", "Strict mode (undeclared assignment throws)", "yes", "'use strict'; try { undeclaredStrictProbeVar = 1; } catch (e) { return e instanceof ReferenceError; } return false;"], ["Platform", "console object", "no", "return typeof console === 'object';"], ["Platform", "Error.stack", "yes", "try { throw new Error('x'); } catch (e) { return typeof e.stack === 'string' && e.stack.length > 0; }"], ["Platform", "Intl", "no", "return new Intl.NumberFormat('en-US').format(3500) === '3,500';"], ["Java", "java.util.HashMap property access", "yes", "var m = new java.util.HashMap(); m.put('k', 'v'); return m.k === 'v';"], ["Java", "java.util.ArrayList keeps Java methods (.add)", "no", "var l = new java.util.ArrayList(); l.add('a'); return true;"]];function pad(s, len) { s = String(s); while (s.length < len) { s = s + " "; } return s;}var result = new Properties();var counts = { yes: 0, partial: 0, no: 0 };var differences = [];for (var i = 0; i < tests.length; i++) { var group = tests[i][0]; var feature = tests[i][1]; var expected = tests[i][2]; var body = tests[i][3]; var status; var detail = ""; try { var value = eval("(function () {\n" + body + "\n})()"); if (value === true) { status = "yes"; } else if (value === false) { status = "no"; detail = "ran but returned false"; } else { status = "partial"; detail = "ran but returned " + value; } } catch (e) { status = "no"; detail = (e && e.name ? e.name : "Error") + ": " + (e && e.message ? e.message : e); } counts[status]++; result.put(group + " / " + feature, status); var line = pad(status, 8) + pad(group, 9) + feature + (detail ? " -> " + detail : ""); if (status !== expected) { differences.push(feature + " (9.1.1: " + expected + ", here: " + status + ")"); System.warn(line + " <-- 9.1.1 gives " + expected); } else { System.log(line); }}System.log("==================== SUMMARY ====================");System.log("Total: " + tests.length + " yes: " + counts.yes + " partial: " + counts.partial + " no: " + counts.no);System.log("Differs from 9.1.1 (" + differences.length + "):\n " + differences.join("\n "));return result;
On 9.1.1 the run reports 65 checks with 52 supported, 13 not supported and 0 differences.
Conclusion
Orchestrator 9.1.1 is a big step forward for scripting. Destructuring, default parameters, optional chaining, nullish coalescing, Map/Set and the modern array and object helpers can make workflow code noticeably shorter and safer. Just keep the gotchas list above in mind, and keep code ES5.1 wherever it might also run on 8.x or untested 9.0.x appliances.
Discover more from Cloud Blogger
Subscribe to get the latest posts sent to your email.
Enjoyed this post?
Was this helpful?
One response to “Orchestrator Rhino Engine 8.x vs 9.x – Comprehensive guide to Modern JavaScript in vRO”
-
[…] Compare their feature set at the following link: 🔗 Orchestrator Rhino Engine 8.x vs 9.x – Comprehensive guide to Modern JavaScript in vRO […]





























Leave a Reply