Orchestrator Rhino Engine 8.x vs 9.x – Comprehensive guide to Modern JavaScript in vRO

Which modern JavaScript actually runs in Orchestrator? Every feature re-tested on VCF Operations Orchestrator 9.1.1 against the ES5.1 Rhino in 8.x, with a support table, the edge cases that bite in workflows and a test script.

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 eval inside try/catch, so one unsupported feature doesn’t stop the run. Rhino compiles eval‘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, but Math.max(...arr) is a syntax error. Use Math.max.apply(null, arr).
  • Arrow functions can’t take rest parameters. (...args) => args.length fails to compile. function (...args) {} works.
  • for (const x of list) doesn’t compile. Use let or var in for...of.
  • let in a loop does not create a new binding per iteration. Closures created in a for (let i ...) loop all see the last value (3,3,3 instead of 0,1,2). Declare a let inside the loop body or use forEach.
  • Reassigning a const is 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 using System.log().
  • No Intl. Also, (1234.5).toLocaleString("en-US") throws RangeError: illegal radix 0; call it without arguments or format manually.
  • Java collections are converted. new java.util.ArrayList() becomes a JavaScript array (no .add(), use push). A java.util.HashMap supports property access and .get(), but not .size().
  • Strict mode is partial. Undeclared assignments and writes to frozen objects throw, but this inside 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.

FeatureOrchestrator 8.xOrchestrator 9.1.1Notes on 9.1.1
ECMAScript levelES5.1ES5.1 + most of ES2015–ES2023
Rhino engine1.7R4Newer Rhino (upgraded in 9.1)9.0.x shipped 1.7.15
Arrow functions❌ No✅ YesNo rest parameters in arrows
Classes❌ No❌ Noclass 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✅ YesRegular functions only
Spread in array literals❌ No✅ Yes
Spread in function calls❌ No❌ NoUse fn.apply(null, arr)
Object spread {...o}❌ No✅ Yes
Object rest in destructuring❌ No❌ NoExplicit SyntaxError
let / const⚠️ Basic⚠️ PartialNo per-iteration loop binding; const reassignment ignored
for…of❌ No✅ YesNot with const
Map / Set / WeakMap / WeakSet❌ No✅ Yes
Promises (all, allSettled, finally)❌ No⚠️ PartialCallbacks run after the script returns
Generators✅ Yes (JS 1.7 style)✅ YesES6 style, incl. yield*
Symbols, Symbol.iterator, custom iterables❌ No✅ Yes
Proxy❌ No✅ Yes
Reflect❌ No✅ Yes
Symbol.species❌ No⚠️ PartialExists, but no classes to use it with
super❌ No⚠️ PartialIn 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⚠️ Partialthis in plain calls is still global
console❌ No❌ NoUse System.log
Error.stack❌ No✅ YesFrames are module/action:line (function)
Intl❌ No❌ NotoLocaleString("en-US") throws
Java interop✅ Yes⚠️ PartialJava collections are converted to JS types

Arrow functions ✅

var add = (a, b) => a + b;
System.log(add(5, 3)); // 8
var numbers = [1, 2, 3, 4, 5];
System.log(numbers.map(n => n * 2)); // 2,4,6,8,10
System.log(numbers.filter(n => n % 2 === 0)); // 2,4
var greet = name => {
var message = "Hello, " + name;
return message;
};
System.log(greet("World")); // Hello, World
// Lexical "this" works as expected
var 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 strings
var multiLine = `line 1
line 2`;
System.log(multiLine.split("\n").length); // 2
// Tagged templates
function tag(strings, v1) { return strings[0] + "[" + v1 + "]" + strings[1]; }
System.log(tag`value ${42} here`); // value [42] here
// String.raw keeps backslashes as-is
System.log(String.raw`C:\Users\Admin\Documents`); // C:\Users\Admin\Documents
System.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); // 9007199254740992
System.log(anotherBig * 2n); // 18014398509481984
System.log(typeof bigNum); // bigint
System.log(2n ** 64n); // 18446744073709551616
// Mixing BigInt and Number throws
// var bad = 10n + 5; // TypeError: Cannot convert BigInt to a number
var good = 10n + BigInt(5);
System.log(good); // 15
// Store BigInts as strings in Properties, attributes and outputs
var 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 30
var { country = "USA" } = person; // default value
System.log(country); // USA
var { name: fullName } = person; // renaming
System.log(fullName); // John
var user = { id: 1, details: { name: "Alice", email: "alice@example.com" } };
var { details: { name: userName, email } } = user; // nested
System.log(userName + " " + email); // Alice alice@example.com
var [first, , third = 9, fourth = 4] = [1, 2, 3]; // arrays, holes, defaults
System.log(first + " " + third + " " + fourth); // 1 3 4
var x = 1, y = 2;
[x, y] = [y, x]; // swap
System.log(x + " " + y); // 2 1
function show({ host, port = 443 }) { return host + ":" + port; } // parameters
System.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 params
System.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)); // 15
function greetAll(greeting, ...names) {
return names.map(name => greeting + ", " + name).join(" | ");
}
System.log(greetAll("Hello", "Alice", "Bob")); // Hello, Alice | Hello, Bob
System.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 works
var o = { ...{ a: 1 }, b: 2 };
System.log(o.a + " " + o.b); // 1 2
// NOT supported: spread in a function call
// Math.max(...arr1); // SyntaxError
System.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.14159
function 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 mutable
config.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 iteration
var 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 true
System.log(Array.from(map.keys()).join(",")); // a,b
var 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,5
function* range(n) { for (var i = 0; i < n; i++) { yield i; } }
for (var v of range(4)) { System.log(v); } // 0, 1, 2, 3
System.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 unique
System.log(sym1.toString()); // Symbol(description)
var obj = { visible: 1 };
var privateKey = Symbol("private");
obj[privateKey] = "secret value";
System.log(obj[privateKey]); // secret value
System.log(JSON.stringify(Object.keys(obj))); // ["visible"] - symbols are hidden
System.log(JSON.stringify(obj)); // {"visible":1}
System.log(Symbol.for("app") === Symbol.for("app")); // true
var it = [1, 2, 3][Symbol.iterator]();
System.log(JSON.stringify(it.next())); // {"value":1,"done":false}
// Make your own iterable
var 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 -> Alice
System.log(proxy.city); // get city -> Property not found
proxy.age = 31; // set age = 31
System.log(target.age); // 31
System.log(Reflect.has(target, "name")); // true
System.log(Reflect.get(target, "age")); // 31
Reflect.set(target, "city", "NYC");
System.log(JSON.stringify(Reflect.ownKeys(target))); // ["name","age","city"]
// Practical use: validate input objects
var 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 World
System.log(typeof Symbol.species); // symbol
System.log(Array[Symbol.species] === Array); // true
// Shorthand properties, computed keys, method shorthand, getters
var 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); // 8
var n = 2; n **= 3;
System.log(n); // 8
System.log(10 ** -1); // 0.1
System.log([1, 2, 3].includes(2)); // true
System.log([NaN].includes(NaN) + " " + [NaN].indexOf(NaN)); // true -1
System.log("abc".startsWith("a") + " " + "abc".endsWith("c") + " " + "ab".repeat(2)); // true true abab
var 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 c
System.log("a,b,c".replaceAll(",", "|")); // a|b|c
System.log(Object.hasOwn({ a: 1 }, "a") + " " + Object.hasOwn({}, "toString")); // true false
var 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 1
System.log([1, 2, 3].findLast(x => x < 3)); // 2
System.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); // 2
System.log(vm.disk?.size); // undefined (no TypeError)
System.log(vm.spec?.["cpu"]); // 2
System.log(vm.greet?.()); // Hello!
System.log(vm.sayBye?.()); // undefined
var list = null;
System.log(list?.[0]); // undefined
var val = null;
System.log(val ?? "default"); // default
System.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("😀")); // true
System.log(/^.$/u.test("😀") + " " + /^.$/.test("😀")); // true false
System.log(/\p{Script=Greek}/u.test("α")); // true
var m = "2026-10".match(/(?<year>\d{4})-(?<month>\d{2})/);
System.log(m.groups.year + " " + m.groups.month); // 2026 10
System.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 literals
System.log(JSON.parse('"\u2028"').length); // 1
System.log(Number.EPSILON); // 2.220446049250313e-16
System.log(0.1 + 0.2 === 0.3); // false
System.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
System.log(Number.isInteger(5)); // true
System.log(Number.isSafeInteger(2 ** 53)); // false
System.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
System.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 statement
  • for 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 comprehension
System.log(squares.join(",")); // 1,4,9
var double = function (x) x * 2; // expression closure
System.log(double(4)); // 8
var xml = <vm><name>web01</name><cpu>2</cpu></vm>; // E4X
System.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 undefined
var 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); // object
globalThis.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.error
System.log(typeof Intl); // undefined
// Careful with locale formatting:
// (1234567.891).toLocaleString("en-US"); // RangeError: illegal radix 0
System.log((1234567.891).toLocaleString()); // 1234567.891 (no grouping)
System.log((1234567.891).toFixed(2)); // 1234567.89
System.log(new Date(Date.UTC(2025, 9, 15)).toLocaleDateString("en-US")); // 10/15/25
System.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"]); // value2
System.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); // 1
delete list[0]; // leaves a hole, doesn't remove the item
System.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”

  1. VCF Operations Orchestrator 9.0: Key Architectural Changes Avatar

    […] 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