A technical deep dive into the Rhino JavaScript engine, supported language features, runtime behaviour, limitations and code compatibility in VCF Automation 9.1.1.
1. Introduction
VMware Orchestrator has always relied heavily on JavaScript to implement automation logic. Whether we are developing reusable Actions, writing Scriptable Tasks, processing REST API responses, manipulating vCenter inventory objects or integrating external systems, JavaScript is one of the primary tools available to an Orchestrator developer.
With VCF Automation 9.1.1, the JavaScript execution environment deserves a closer look.
The JavaScript engine used by Orchestrator has evolved, bringing support for a much broader set of JavaScript language features and built-in APIs. Developers can now use several capabilities associated with modern JavaScript, including arrow functions, template literals, destructuring, optional chaining, nullish coalescing, Map, Set and newer Array methods.
However, the availability of modern syntax does not mean that every feature of modern JavaScript is supported. Some features remain unavailable, others have partial support, and certain behaviours differ because JavaScript executes inside the Rhino engine and the Orchestrator platform.
There are also considerations beyond the JavaScript language itself. Java interoperability, security restrictions, access to system-level functionality and the way asynchronous callbacks execute can all influence whether an Action behaves as expected.
For teams developing and maintaining Orchestrator automation, understanding these details is important.
This article focuses specifically on JavaScript code compatibility in VMware Orchestrator running on VCF Automation 9.1.1. We will examine the engine, explore the supported language features with practical examples, identify unsupported capabilities and discuss runtime behaviour that developers should be aware of.
The objective is not simply to list the features available in the JavaScript engine. It is to understand how those features behave in Orchestrator and what developers should consider when using them in real automation code.
2. Understanding the JavaScript Engine in VCF Automation 9.1.1
VMware Orchestrator uses Mozilla Rhino as its JavaScript engine.
Rhino is a JavaScript engine implemented in Java. Unlike Node.js, which provides a JavaScript runtime with its own ecosystem of modules and APIs, Rhino executes JavaScript inside a Java application and provides interoperability with Java classes.
This is particularly relevant to Orchestrator because its scripting environment exposes Java-based platform objects and VMware-specific APIs to JavaScript.
The Rhino engine version is therefore an important factor in determining which JavaScript language features are available.
The version progression reported in the technical reference material is:
| Product version | Reported Rhino version |
|---|---|
| Aria Automation 8.18.x | Rhino 1.7R4 |
| VCF Automation 9.0 | Rhino 1.7.15 |
| VCF Automation 9.1 | Rhino 1.8.0 |
| VCF Automation 9.1.1 | Rhino 1.9.1 |
FYI – https://github.com/mozilla/rhino/releases
The exact engine version should be verified against the appliance build being investigated. The version information above is a reported progression, not an independently verified runtime inspection of every 9.1.1 build.
2.1 Why does the Rhino version matter?
JavaScript has evolved considerably over the years. Many features that developers now use routinely were introduced after the original ES5 specification.
Examples include:
- Arrow functions.
- Template literals.
- Destructuring.
- Default and rest parameters.
- Spread syntax.
- Map and Set.
- Generators and iterators.
- Optional chaining.
- Nullish coalescing.
- Modern Array and String methods.
The availability of these features depends on the JavaScript engine.
For Orchestrator developers, this means the engine determines not only which syntax can be written but also which built-in objects, methods and runtime behaviours are available.
2.2 Rhino is not Node.js
One important point needs to be understood before using modern JavaScript features in Orchestrator.
Although the Rhino engine supports many modern JavaScript capabilities, Orchestrator does not become a Node.js environment.
For example, the following Node.js code is not automatically available inside an Orchestrator Action:
const fs = require("fs");const path = require("path");
Node.js modules such as fs, path, http and process are not part of the standard Orchestrator JavaScript environment.
Instead, Orchestrator provides its own scripting objects, APIs and integrations.
For example:
System.log("Starting VM provisioning");var vmName = "APP-SERVER-01";System.log("Provisioning VM: " + vmName);
The JavaScript language and the APIs exposed by the host platform are two separate things. A feature being supported by Rhino does not automatically make a Node.js API available.
3. JavaScript Language Feature Compatibility
This section examines the JavaScript language features available in VCF Automation 9.1.1 Orchestrator.
The following matrix consolidates the feature compatibility information from the supplied technical reference. It should be treated as a reference compatibility assessment; individual behaviours should still be verified on the target appliance build.
3.1 Language and syntax support
| Feature | Support in 9.1.1 | Notes |
|---|---|---|
| ES5.1 syntax | Supported | Core JavaScript syntax |
| ES2015–ES2023 features | Broad support | Not every feature is supported |
| Arrow functions | Supported | Rest parameters in arrow functions are not supported |
| Template literals | Supported | Includes multiline strings and interpolation |
String.raw | Supported | Tagged template literal support |
| Classes | Not supported | class remains a reserved word |
| Default parameters | Supported | Available in regular functions |
| Rest parameters | Supported | Regular functions only |
| Spread syntax for arrays | Supported | Expands iterable values into arrays |
| Spread in function calls | Not supported | Use apply() as an alternative |
| Object spread | Supported | Copies enumerable object properties |
| Array destructuring | Supported | Supports nested patterns and defaults |
| Object destructuring | Supported | Supports nested patterns and defaults |
| Destructured function parameters | Supported | Useful for handling structured input |
| Object shorthand properties | Supported | Shorter object declarations |
| Computed property names | Supported | Dynamic object keys |
| Object literal methods | Supported | Method shorthand syntax |
| Exponentiation operator | Supported | ** and **= |
| Optional chaining | Supported | ?. |
| Nullish coalescing | Supported | ?? |
| Logical assignment | Supported | &&=, ||=, ??= |
| Numeric separators | Supported | For example, 1_000_000 |
3.2 Arrow functions
Arrow functions provide a shorter way to define functions.
Traditional function:
var multiply = function (a, b) { return a * b;};System.log(multiply(10, 5));
Arrow function:
var multiply = (a, b) => { return a * b;};System.log(multiply(10, 5));
For simple expressions, the return statement and braces can be omitted:
var multiply = (a, b) => a * b;System.log(multiply(10, 5));
This is useful when working with arrays and callback functions.
For example, imagine an Action receiving a list of VM names:
var vmNames = [ "APP01", "DB01", "WEB01"];var upperCaseNames = vmNames.map(name => name.toUpperCase());System.log(upperCaseNames);
Arrow functions can make callback-heavy code easier to read.
Important limitation: Rest parameters in arrow functions are not supported according to the supplied compatibility matrix. Do not assume every ES2015 arrow-function capability is available.
3.3 Template literals
Template literals allow variables and expressions to be embedded directly inside strings.
Traditional concatenation:
var vmName = "APP01";var environment = "Production";var message = "VM " + vmName + " belongs to " + environment;System.log(message);
Using template literals:
var vmName = "APP01";var environment = "Production";var message = `VM ${vmName} belongs to ${environment}`;System.log(message);
They also support multiline strings:
var emailBody = `VM Provisioning CompletedVM Name: ${vmName}Environment: ${environment}Status: Successful`;System.log(emailBody);
This is particularly useful when constructing email content, JSON fragments and API payloads.
The String.raw tag is also supported:
var path = String.raw`C:\Automation\Scripts`;System.log(path);
Unlike a normal template literal, String.raw preserves raw escape sequences in the template.
3.4 Destructuring
Destructuring allows values to be extracted from arrays and objects without repeatedly accessing their properties.
Array destructuring
var vmDetails = [ "APP01", "Windows", "Production"];var [vmName, operatingSystem, environment] = vmDetails;System.log(vmName);System.log(operatingSystem);System.log(environment);
Object destructuring
var vm = { name: "APP01", os: "Windows", environment: "Production"};var { name, os, environment } = vm;System.log(name);System.log(os);System.log(environment);
Nested destructuring
var response = { vm: { name: "APP01", network: { ipAddress: "172.20.10.15" } }};var { vm: { name, network: { ipAddress } }} = response;System.log(name);System.log(ipAddress);
This can be particularly helpful when processing nested REST API responses.
Destructuring with default values
var vm = { name: "APP01"};var { name, environment = "Development"} = vm;System.log(name);System.log(environment);
The default value is used when the property is missing or its value is undefined.
Object rest syntax is not supported according to the supplied compatibility matrix. Therefore, avoid assuming that this works:
var { name, ...otherProperties} = vm;
3.5 Default and rest parameters
Default parameters allow function arguments to have predefined values.
function createVM(name, environment = "Development") { System.log("VM Name: " + name); System.log("Environment: " + environment);}createVM("APP01");createVM("DB01", "Production");
Rest parameters allow a function to receive multiple arguments as an array.
function logMessages() { var messages = Array.prototype.slice.call(arguments); messages.forEach(function(message) { System.log(message); });}logMessages("Provisioning started", "Network configured", "Provisioning completed");
The compatibility matrix reports support for rest parameters in regular functions. For example:
function logMessages(...messages) { messages.forEach(function(message) { System.log(message); });}logMessages("Provisioning started", "Network configured", "Completed");
However, rest parameters in arrow functions are not supported in the supplied reference.
3.6 Spread syntax
Spread syntax expands an iterable into individual elements.
Array spread
var windowsVMs = ["APP01", "APP02"];var linuxVMs = ["DB01", "DB02"];var allVMs = [...windowsVMs, ...linuxVMs];System.log(allVMs);
Object spread
var defaultConfig = { environment: "Development", os: "Windows"};var productionConfig = { ...defaultConfig, environment: "Production"};System.log(productionConfig.environment);
This is useful when creating new configuration objects based on existing values.
Important limitation: Spread in function calls
The supplied compatibility matrix identifies spread syntax in function calls as unsupported.
For example, do not rely on this:
function createVM(name, os, environment) { System.log(name + " - " + os + " - " + environment);}var details = ["APP01", "Windows", "Production"];createVM(...details);
Use apply() instead:
createVM.apply(null, details);
This distinction matters because array spread and function-call spread look similar but are separate language operations.
4. Variables, Scope and Function Behaviour
The availability of let and const does not mean their behaviour is identical to every modern JavaScript runtime.
The supplied compatibility matrix identifies partial support for these declarations.
4.1 var, let and const
Traditional declaration:
var vmName = "APP01";
Using let:
let vmName = "APP01";vmName = "DB01";System.log(vmName);
Using const:
const environment = "Production";System.log(environment);
However, the reference identifies two important limitations:
- Per-iteration loop binding is not supported in the expected modern manner.
- Reassignment of a
constvariable may be silently ignored rather than producing the expected error.
For example:
const environment = "Production";environment = "Development";System.log(environment);
Do not assume that a reassignment will behave exactly as it does in a modern browser or Node.js runtime.
For critical automation logic, test the actual behaviour on the target appliance.
4.1.1 An Important let Loop Behaviour
Although let is supported, its behaviour inside loops has an important limitation in the Orchestrator 9.1.1 JavaScript environment.
In standard modern JavaScript, a let declaration in a for loop creates a separate binding for each iteration.
Consider the following example:
var functions = [];for (let i = 0; i < 3; i++) { functions.push(function () { return i; });}System.log( functions.map(function (fn) { return fn(); }).join(","));
Expected output in standard modern JavaScript:
0,1,2
Reported output in Orchestrator 9.1.1:
3,3,3
Why does this happen?
The functions created during the loop do not capture a separate value of i for each iteration. Instead, they reference the same loop variable, which has reached its final value when the functions are executed.
Workaround
If your automation logic depends on capturing a separate value during each iteration, declare the variable inside the loop body:
var functions = [];for (var i = 0; i < 3; i++) { let currentIndex = i; functions.push(function () { return currentIndex; });}System.log( functions.map(function (fn) { return fn(); }).join(","));
Expected output:
0,1,2
Developer takeaway: Although let is supported, do not automatically assume that its per-iteration closure behaviour is identical to modern JavaScript engines. Pay particular attention to callbacks and functions created inside loops.
4.2 Loop behaviour
for...of is supported, but the compatibility matrix identifies a limitation when using const in this construct.
A basic example using var:
var vmNames = ["APP01", "DB01", "WEB01"];for (var vmName of vmNames) { System.log(vmName);}
Avoid assuming this equivalent form is supported:
for (const vmName of vmNames) { System.log(vmName);}
This is one of the reasons developers should test individual language features rather than treating the presence of modern syntax as proof of complete ECMAScript compatibility.
5. Modern Built-in Objects and Collections
VCF Automation 9.1.1 Orchestrator supports several modern JavaScript collection types.
These are useful when processing inventory data, API responses, lookup results and configuration information.
5.1 Map
A Map stores key-value pairs.
Unlike a normal JavaScript object, a Map can use values of different types as keys and provides methods specifically designed for collection management.
var vmMap = new Map();vmMap.set("APP01", { os: "Windows", environment: "Production"});vmMap.set("DB01", { os: "Linux", environment: "Development"});System.log(vmMap.get("APP01").os);System.log(vmMap.size);
Other useful methods include:
set()get()has()delete()clear()keys()values()entries()
5.2 Set
A Set stores unique values.
This is useful when removing duplicate VM names, IP addresses or other values from a collection.
var ipAddresses = [ "172.20.10.10", "172.20.10.11", "172.20.10.10", "172.20.10.12"];var uniqueIPs = new Set(ipAddresses);System.log(uniqueIPs.size);uniqueIPs.forEach(function(ip) { System.log(ip);});
The duplicate IP address is stored only once.
5.3 WeakMap and WeakSet
WeakMap and WeakSet are also listed as supported.
They provide weak references to objects, meaning their entries do not prevent the referenced objects from being garbage-collected.
For most Orchestrator Actions, Map and Set will be more commonly useful. WeakMap and WeakSet are specialised tools and should be used only when their reference and lifecycle behaviour is appropriate.
5.4 Object.values(), Object.entries() and Object.fromEntries()
These methods make object processing easier.
var vm = { name: "APP01", os: "Windows", environment: "Production"};var values = Object.values(vm);System.log(values);
To retrieve key-value pairs:
var entries = Object.entries(vm);entries.forEach(function(entry) { System.log(entry[0] + ": " + entry[1]);});
To create an object from key-value pairs:
var vmDetails = [ ["name", "APP01"], ["os", "Windows"], ["environment", "Production"]];var vm = Object.fromEntries(vmDetails);System.log(vm.name);
Object.getOwnPropertyDescriptors() is also listed as supported and can be useful when examining object property definitions.
5.5 Getter Properties
Getter properties allow you to expose a value through a property while executing a function when that property is accessed.
var vm = { name: "APP01", environment: "Production", get details() { return this.name + " - " + this.environment; }};System.log(vm.details);
Expected output:
APP01 - Production
Here, details is accessed like a normal property, but the getter executes when the property is read.
5.6 Object.setPrototypeOf()
Object.setPrototypeOf() allows you to assign a prototype to an existing object. This enables the object to inherit properties and methods from another object.
var baseVM = { getEnvironment: function() { return "Production"; }};var vm = { name: "APP01"};Object.setPrototypeOf(vm, baseVM);System.log(vm.name);System.log(vm.getEnvironment());
Expected output:
APP01Production
In this example, vm does not define getEnvironment() directly. It can still call the method because baseVM is assigned as its prototype.
Compatibility note: These examples reflect the reported JavaScript compatibility findings for VCF Automation 9.1.1. Support for these language features does not mean every JavaScript object or Java-backed object in Orchestrator behaves identically.
6. Modern Array and String Methods
Several useful Array and String methods are available in the 9.1.1 JavaScript environment.
These methods can reduce the amount of custom JavaScript required for routine data processing.
6.1 Array.includes()
Checks whether an array contains a specified value.
var allowedOS = ["Windows", "Linux", "RHEL"];var requestedOS = "Linux";if (allowedOS.includes(requestedOS)) { System.log("Operating system is supported");}
6.2 Array.flat() and Array.flatMap()
flat() creates a new array with nested arrays flattened to a specified depth.
var networkGroups = [ ["Network-A", "Network-B"], ["Network-C"], ["Network-D", "Network-E"]];var allNetworks = networkGroups.flat();System.log(allNetworks);
flatMap() combines mapping and flattening:
var vmGroups = [ { name: "Production", vms: ["APP01", "DB01"] }, { name: "Development", vms: ["DEV01", "DEV02"] }];var allVMs = vmGroups.flatMap(group => group.vms);System.log(allVMs);
6.3 Array.at()
The at() method retrieves an element using a positive or negative index.
var workflowSteps = [ "Validation", "Provisioning", "Configuration", "Completion"];System.log(workflowSteps.at(0));System.log(workflowSteps.at(-1));
The negative index retrieves an element relative to the end of the array.
6.4 Array.findLast()
Returns the last element matching a specified condition.
var taskResults = [ { task: "Validation", status: "Success" }, { task: "Provisioning", status: "Failed" }, { task: "Configuration", status: "Success" }, { task: "Deployment", status: "Failed" }];var lastFailure = taskResults.findLast( result => result.status === "Failed");System.log(lastFailure.task);
6.5 String.padStart() and String.padEnd()
These methods add padding to a string.
var requestNumber = "125";var formattedNumber = requestNumber.padStart(8, "0");System.log(formattedNumber);
Output:
00000125
padEnd() can be used to add characters to the end of a string.
6.6 String.trimStart() and String.trimEnd()
These methods remove whitespace from the beginning or end of a string.
var vmName = " APP01 ";System.log(vmName.trimStart());System.log(vmName.trimEnd());
6.7 String.replaceAll()
Replaces all occurrences of a matching string.
var networkName = "Production_Network_Production";var result = networkName.replaceAll("Production", "PROD");System.log(result);
Output:
PROD_Network_PROD
6.8 Object.hasOwn()
Checks whether an object directly contains a specified property.
var vm = { name: "APP01", os: "Windows"};System.log(Object.hasOwn(vm, "name"));System.log(Object.hasOwn(vm, "environment"));
This is useful when validating API response objects or optional Action inputs.
6.9 Array.from()
Array.from() creates a new JavaScript array from an iterable or array-like object.
This is useful when converting collections or other iterable values into native JavaScript arrays.
var vmNames = new Set([ "APP01", "DB01", "WEB01"]);var vmArray = Array.from(vmNames);System.log(vmArray);System.log(Array.isArray(vmArray));
Output:
APP01,DB01,WEB01true
It also supports a mapping function:
var vmNames = ["app01", "db01", "web01"];var upperCaseNames = Array.from( vmNames, function(name) { return name.toUpperCase(); });System.log(upperCaseNames);
6.10 Array.of()
Array.of() creates an array containing the supplied arguments.
var vmDetails = Array.of( "APP01", "Windows", "Production");System.log(vmDetails);
Output:
APP01,Windows,Production
One useful distinction is the behaviour of Array.of() compared with the Array constructor:
var first = Array(3);var second = Array.of(3);System.log(first.length);System.log(second.length);
Output:
31
Array(3) creates an array with three empty slots, whereas Array.of(3) creates an array containing the number 3.
6.11 Array.find()
find() returns the first element that satisfies a specified condition.
Consider a list of VM details:
var vmList = [ { name: "APP01", os: "Windows" }, { name: "DB01", os: "Linux" }, { name: "WEB01", os: "Linux" }];var selectedVM = vmList.find(function(vm) { return vm.name === "DB01";});System.log(selectedVM.name);System.log(selectedVM.os);
Output:
DB01Linux
This is useful when retrieving a specific object from an array based on a property value.
6.12 Array.findIndex()
findIndex() returns the index of the first element that satisfies a specified condition.
var vmNames = [ "APP01", "DB01", "WEB01"];var index = vmNames.findIndex(function(name) { return name === "DB01";});System.log(index);
Output:
1
If no matching element is found, the method returns -1.
6.13 String.startsWith()
startsWith() checks whether a string begins with a specified value.
var vmName = "PROD-APP01";System.log(vmName.startsWith("PROD"));System.log(vmName.startsWith("DEV"));
Output:
truefalse
This can be useful when validating naming conventions or identifying environments based on VM names.
6.14 String.endsWith()
endsWith() checks whether a string ends with a specified value.
var hostname = "app01.example.com";System.log(hostname.endsWith(".com"));System.log(hostname.endsWith(".in"));
Output:
truefalse
This is useful when validating domain suffixes, file extensions and other string patterns.
6.15 String.repeat()
repeat() creates a new string by repeating the original string a specified number of times.
var separator = "-".repeat(30);System.log(separator);
Output:
------------------------------
It can also be useful when formatting log messages or generating readable text output.
Developer takeaway: These additional methods provide convenient ways to process collections and strings without writing repetitive loops and custom utility functions. They are particularly useful when working with VM inventory data, API responses, naming conventions and configuration values.
7. Advanced JavaScript Features
The Rhino engine also supports several advanced JavaScript features.
These may not be required in every Action, but they expand the options available when developing reusable JavaScript logic.
7.1 Generators
Generators are functions that can pause and resume execution using the yield keyword.
function* getVMNames() { yield "APP01"; yield "DB01"; yield "WEB01";}var vmIterator = getVMNames();System.log(vmIterator.next().value);System.log(vmIterator.next().value);System.log(vmIterator.next().value);
Generators can be useful when producing values incrementally rather than constructing an entire collection immediately.
The generator delegation operator yield* is also listed as supported.
7.2 Symbols and custom iterators
Symbols provide unique identifiers that can be used as object property keys.
var vmId = Symbol("vmId");var vm = { name: "APP01"};vm[vmId] = 1001;System.log(vm.name);System.log(vm[vmId]);
The Symbol.iterator mechanism is also supported.
This allows objects to define custom iteration behaviour and participate in iterable operations such as for...of.
7.3 Proxy and Reflect
Proxy allows developers to intercept operations performed on objects.
var vm = { name: "APP01", environment: "Production"};var handler = { get: function(target, property) { System.log("Accessing property: " + property); return target[property]; }};var proxyVM = new Proxy(vm, handler);System.log(proxyVM.name);
Reflect provides methods for performing certain object operations.
Proxy and Reflect can be useful for advanced object handling, validation and controlled access to properties.
7.4 Object literal methods and computed properties
Modern object literal syntax is supported.
var propertyName = "environment";var vm = { name: "APP01", [propertyName]: "Production", getDetails() { return this.name + " - " + this.environment; }};System.log(vm.getDetails());
The method shorthand and computed property name make object declarations more concise.
7.5 The super keyword
The compatibility matrix identifies partial support for super, specifically in object-literal methods.
This should not be confused with class-based inheritance. JavaScript classes are not supported in the reported compatibility matrix.
7.6 Symbol.species
Symbol.species is listed as partially supported.
Its availability does not imply that every related class-based inheritance pattern can be used, because the class syntax itself is not supported.
8. Promise and Asynchronous Behaviour
Promise support is an important area to understand when developing JavaScript Actions in Orchestrator 9.1.1.
The Rhino engine provides support for Promises, including:
Promise.all()Promise.allSettled()Promise.finally()
However, Promise support does not mean that asynchronous callbacks execute in the same way as they do in Node.js or browser-based JavaScript environments.
One important behaviour reported in Orchestrator 9.1.1 is that Promise callbacks execute after the main Action or Scriptable Task has completed.
Let’s understand this with a simple example.
8.1 Understanding Promise callback execution
Consider the following code:
var results = [];Promise.resolve("Completed").then(function(value) { results.push(value);});System.log("Results length: " + results.length);
A developer might expect the Promise callback to execute before the next statement.
However, the reported output in Orchestrator 9.1.1 is:
Results length: 0
The Promise callback has not executed when the System.log() statement runs.
This happens because the callback is deferred rather than executed immediately as part of the current synchronous script execution.
8.2 Promise callbacks execute after the script completes
Consider another example:
Promise.resolve("Promise callback executed").then(function(result) { System.log(result);});System.log("Main script completed");
The important observation is that the Promise callback is not executed as part of the normal synchronous execution sequence.
The reported Orchestrator behaviour is that the callback executes after the Action or Scriptable Task has completed.
This is different from the execution model developers might expect when working with Promises in Node.js or browser environments.
8.3 Why does this matter for Orchestrator Actions?
Consider a situation where an Action needs to retrieve information asynchronously and return the result.
var vmDetails;Promise.resolve({ name: "APP01", os: "Windows"}).then(function(result) { vmDetails = result;});return vmDetails;
A developer might expect vmDetails to contain the resolved object when the Action returns.
However, because the Promise callback has not executed at that point, the Action may return an undefined value.
This makes the following assumption unsafe:
Starting a Promise inside an Action guarantees that its callback will complete before the Action returns.
8.4 Practical recommendation
For Orchestrator development, do not treat Promise callbacks as a replacement for the platform’s workflow execution model.
If an operation must complete before the next workflow step executes, use an appropriate synchronous Action or separate workflow elements to control execution order.
Avoid relying on deferred Promise callbacks to populate Action outputs or modify workflow state after the script has completed.
8.5 What about async/await?
Although Promises are supported, async and await syntax are not supported according to the supplied compatibility reference.
The following example should therefore not be used:
async function getVMDetails() { var result = await getDetails(); return result;}
The same limitation applies to:
for await...of- Top-level await
- Async iteration
Developer takeaway: Promise availability does not imply full asynchronous JavaScript support. Understand the Orchestrator execution lifecycle before using Promises, particularly when Action outputs and workflow execution depend on the result.
8.6 Promise Executor Runs Synchronously
When a Promise is created, its executor function runs immediately. However, callbacks registered through .then() are deferred.
Consider the following example:
System.log("Before Promise");var promise = new Promise(function(resolve) { System.log("Inside Promise executor"); resolve("Completed");});promise.then(function(value) { System.log("Inside then callback: " + value);});System.log("After Promise");
Synchronous execution order:
Before PromiseInside Promise executorAfter Promise
The Promise executor runs immediately when the Promise is created. The .then() callback is deferred and, based on the reported Orchestrator 9.1.1 behaviour, may execute after the Action or Scriptable Task has completed.
This distinction is important when writing Orchestrator code. Do not assume that code inside .then() will finish before the Action returns its output or before the next workflow element executes.
Practical recommendation: For operations that must complete before an Action returns, use synchronous execution or orchestrate the operation through separate workflow elements rather than relying on deferred Promise callbacks.
9. Unsupported and Partially Supported Features
Not every modern JavaScript feature is available in Orchestrator 9.1.1.
Developers should pay particular attention to the following limitations.
| Feature | Compatibility | Consideration |
|---|---|---|
| JavaScript classes | Unsupported | class syntax cannot be used |
| Async/await | Unsupported | Use supported synchronous patterns and workflow orchestration |
| Top-level await | Unsupported | Not available |
| Async iteration | Unsupported | for await...of cannot be used |
| Rest parameters in arrow functions | Unsupported | Use a regular function where required |
| Object rest syntax | Unsupported | Extract required properties individually |
| Spread in function calls | Unsupported | Use Function.apply() |
| Per-iteration loop binding | Partial | Validate let/const loop behaviour |
| Const reassignment | Partial | Do not depend on a particular error behaviour |
| Symbol.species | Partial | Class-based use cases are constrained |
super | Partial | Object-literal method support only |
Strict-mode this | Partial | Plain function calls may still expose the global object |
9.1 Why class syntax matters
Developers coming from modern JavaScript environments may naturally expect to use classes to organize reusable logic.
For example:
class VMManager { constructor(name) { this.name = name; } getName() { return this.name; }}
However, class syntax is listed as unsupported.
A function-based constructor can be used instead:
function VMManager(name) { this.name = name;}VMManager.prototype.getName = function() { return this.name;};var vm = new VMManager("APP01");System.log(vm.getName());
This is a useful alternative when organizing reusable JavaScript logic inside Actions.
9.2 Strict mode and this
Strict mode has a behaviour worth noting.
In standard modern JavaScript, calling a regular function without an explicit receiver in strict mode makes this undefined.
The supplied compatibility reference identifies that this in plain function calls can still refer to the global object.
Developers should avoid depending on assumptions about strict-mode this behaviour when writing reusable functions.
10. Regular Expression and Numeric Support
The compatibility matrix identifies broad support for modern regular expression features.
These include:
- Sticky flag (
y). - Unicode flag (
u). - Unicode code point escapes (
\u{...}). - Unicode property escapes (
\p{...}). - Named capture groups.
- DotAll flag (
s). - Lookbehind assertions.
String.matchAll().
For example, named capture groups can make regular expression results easier to understand:
var pattern = /(?<hostname>[A-Z0-9]+)-(?<environment>[A-Z]+)/;var result = pattern.exec("APP01-PROD");if (result) { System.log(result.groups.hostname); System.log(result.groups.environment);}
The numeric APIs listed as supported include:
Number.EPSILONNumber.isInteger()Number.MAX_SAFE_INTEGER
Numeric separators are also supported.
var totalMemory = 1_024_000;System.log(totalMemory);System.log(Number.isInteger(totalMemory));
BigInt is listed as supported as well.
var largeValue = 9007199254740993n;System.log(largeValue);
When using BigInt, remember that JSON serialization does not automatically support BigInt values. Conversion may be necessary before sending data through an API or serializing it as JSON.
11. Legacy Rhino Features and Other Runtime Details
The newer Rhino environment also retains some legacy JavaScript features that are not commonly used in modern JavaScript development.
The supplied compatibility matrix identifies support for:
- Array comprehensions.
- Expression closures.
- E4X XML literals.
Expression closures are listed as supported in the 9.1.1 reference, while E4X provides legacy XML-related syntax.
These are specialised features. Their presence should not be interpreted as a recommendation to use them in new automation code.
11.1 Global Objects, Intl and Locale Formatting
The availability of standard JavaScript global objects and internationalization APIs can differ between JavaScript environments.
In Orchestrator 9.1.1, the Intl object is reported as unavailable.
For example:
System.log(typeof Intl);
Output:
undefined
This means developers cannot rely on the standard Intl.NumberFormat, Intl.DateTimeFormat and other internationalization APIs.
There is also an important distinction in how toLocaleString() behaves.
Number formatting
Consider the following example:
var amount = 1234567.891;System.log(amount.toLocaleString("en-US"));
The reported behaviour in Orchestrator 9.1.1 is an error:
RangeError: illegal radix 0
Therefore, do not assume that locale-based number formatting works in the same way as it does in modern browsers or Node.js.
Date formatting
Date formatting behaves differently.
var date = new Date(Date.UTC(2025, 9, 15));System.log(date.toLocaleDateString("en-US"));
Reported output:
10/15/25
This demonstrates that date locale formatting can work even though the global Intl object is unavailable.
Developer takeaway: Do not assume that the availability of toLocaleString() on one JavaScript object guarantees identical behaviour on another. If your Actions depend on locale-specific number or date formatting, validate the exact method and format required by your automation.
12. Java Interoperability: An Important Part of Orchestrator Code
Java interoperability is one of the characteristics that makes Rhino useful in Orchestrator.
Because Rhino executes within a Java application, Java classes and objects can be exposed to JavaScript.
For example:
var map = new java.util.HashMap();map.put("hostname", "APP01");map.put("environment", "Production");System.log(map.get("hostname"));
This type of interaction is commonly encountered in scripts that work with Java-based integrations and platform objects.
However, developers should distinguish between Java interoperability and unrestricted access to the Java runtime.
Access to Java classes may be subject to platform restrictions, security controls and the execution context.
12.1 Java Collections and JavaScript Conversion
One important behaviour to understand in Orchestrator 9.1.1 is how Java collections are exposed to JavaScript.
Although Java collections originate from Java classes, they may be presented to JavaScript as JavaScript-friendly objects or arrays.
This means that developers should not automatically assume that every Java collection method will remain available after accessing the object from JavaScript.
HashMap behaviour
Consider the following example:
var map = new java.util.HashMap();map.put("hostname", "APP01");map.put("environment", "Production");System.log(map.hostname);System.log(map.get("hostname"));
The tested behaviour reported for VCF Automation 9.1.1 includes:
| Operation | Behaviour |
|---|---|
map.put() | Supported |
map.get() | Supported |
map.hostname | Supported |
map.size() | Not available |
For example, calling map.size() may result in a TypeError because the object is exposed with JavaScript-friendly property access rather than behaving exactly like the original Java HashMap.
ArrayList behaviour
Now consider a Java ArrayList:
var list = new java.util.ArrayList();list.push("APP01");list.push("DB01");list.push("WEB01");System.log(Array.isArray(list));System.log(list.length);System.log(list[0]);
The reported behaviour is that the ArrayList is exposed as a JavaScript array.
| Operation | Behaviour |
|---|---|
list.push() | Supported |
list.length | Supported |
list[index] | Supported |
Array.isArray(list) | Returns true |
list.add() | Not available |
This is an important distinction. Even though the original object was created using java.util.ArrayList, JavaScript may interact with it as an array.
Important consideration:
When working with Java collections in Orchestrator 9.1.1, developers should understand how the objects are exposed to JavaScript rather than assuming that all original Java methods remain available.
Where appropriate, use native JavaScript arrays and objects for data manipulation. Use Java collection methods only when they are actually exposed and supported in the execution environment.
Additional Behaviour: Using delete on an ArrayList-derived Array
There is another interesting behaviour to consider when working with Java collections converted into JavaScript arrays.
Consider the following example:
var list = new java.util.ArrayList();list.push("APP01");list.push("DB01");list.push("WEB01");delete list[0];System.log(JSON.stringify(list));
Reported output:
[null,"DB01","WEB01"]
Why does this happen?
The Java ArrayList is exposed as a JavaScript array. When the JavaScript delete operator is used, it removes the property at the specified index but does not change the array length or shift the remaining elements.
As a result, the first position becomes an empty slot, which is represented as null when the array is serialized using JSON.stringify().
Developer takeaway: If you want to remove an element and shift the remaining elements, use an appropriate array operation such as splice() instead of delete.
list.splice(0, 1);System.log(JSON.stringify(list));
Expected output:
["DB01","WEB01"]
This distinction is important when manipulating Java collections exposed as JavaScript arrays, particularly when preparing data for REST API requests or JSON serialization.
12.2 Java Runtime Restrictions and Operating-System Command Execution
Java interoperability does not mean that Orchestrator scripts have unrestricted access to the underlying Java runtime.
Certain Java APIs can be restricted for security reasons, particularly APIs that allow scripts to execute operating-system commands directly.
This is an important consideration for developers maintaining existing Orchestrator Actions that depend on local command execution.
Using java.lang.Runtime.exec()
Consider the following example:
var runtime = java.lang.Runtime.getRuntime();var process = runtime.exec("hostname");
This approach attempts to execute the hostname command directly from the JavaScript Action.
However, the reported behaviour in VCF Automation 9.1.1 is that this process-execution operation is restricted.
Using java.lang.ProcessBuilder
Another commonly used Java mechanism is ProcessBuilder.
var processBuilder = new java.lang.ProcessBuilder( "hostname");var process = processBuilder.start();
This approach is also reported to be restricted in the VCF Automation 9.1.1 environment.
What about the Orchestrator Command interface?
The Orchestrator Command interface is another mechanism developers may have used to execute operating-system commands.
Broadcom has documented the removal of this interface in VCF Automation 9.x for security reasons.
It is important to understand that the Command interface, Runtime.exec() and ProcessBuilder are different APIs. Their restrictions should not be treated as one single JavaScript engine limitation.
Impact on existing Orchestrator automation
If your existing Actions use local command execution, review those dependencies carefully.
Common examples include:
- Executing shell scripts or batch files.
- Running PowerShell commands.
- Calling local system utilities.
- Performing operating-system-level operations.
- Executing external scripts through Java process APIs.
What should developers consider instead?
Depending on the requirement, consider supported alternatives such as:
- PowerShell integrations for Windows operations.
- Python integrations for script execution.
- REST APIs exposed by external systems.
- Dedicated automation endpoints or services.
- Appropriate Orchestrator integrations.
The alternative should be selected based on the actual execution requirement and the security controls of the environment.
Developer takeaway: JavaScript support and Java interoperability do not imply unrestricted operating-system access. If an existing Action depends on Runtime.exec(), ProcessBuilder or the Orchestrator Command interface, treat it as a specific migration consideration in VCF Automation 9.1.1.
13. Orchestrator-Specific APIs and Execution Context
JavaScript language compatibility is only one part of the overall execution environment.
Orchestrator provides its own objects and APIs that scripts use to interact with the platform.
For example:
System.log("Starting automation");
Orchestrator developers also work with:
- Workflow input and output parameters.
- Configuration elements.
- Resource elements.
- Inventory objects.
- vCenter and other plugin objects.
- REST hosts and REST operations.
- Workflow tokens and execution states.
These APIs are not standard JavaScript objects. Their behaviour is determined by the Orchestrator platform and installed plugins.
Consequently, a successful JavaScript feature test does not prove that an Action using VMware inventory objects or plugin-specific methods will work correctly.
For production code, it is important to validate both the JavaScript operation and the actual Orchestrator objects involved.
14. Practical Compatibility Test Suite
A useful way to assess the 9.1.1 execution environment is to run small, isolated tests.
The following examples can be used as a starting point for a dedicated Orchestrator Action.
Test 1 — Arrow functions and template literals
var vmName = "APP01";var environment = "Production";var getDetails = (name, env) => `${name} - ${env}`;System.log(getDetails(vmName, environment));
Expected output:
APP01 - Production
Test 2 — Destructuring
var vm = { name: "APP01", os: "Windows", environment: "Production"};var { name, os, environment } = vm;System.log(name);System.log(os);System.log(environment);
Test 3 — Map and Set
var vmMap = new Map();vmMap.set("APP01", "Windows");vmMap.set("DB01", "Linux");System.log(vmMap.get("APP01"));var ipList = new Set([ "172.20.10.10", "172.20.10.11", "172.20.10.10"]);System.log(ipList.size);
Test 4 — Optional chaining
var response = { vm: { name: "APP01" }};var vmName = response.vm?.name;System.log(vmName);
Test 5 — Nullish coalescing
var requestedEnvironment = null;var environment = requestedEnvironment ?? "Development";System.log(environment);
Test 6 — Object and Array methods
var vm = { name: "APP01", os: "Windows"};System.log(Object.values(vm));var vmNames = ["APP01", "DB01", "WEB01"];System.log(vmNames.includes("DB01"));System.log(vmNames.at(-1));
Test 7 — Unsupported syntax
Test unsupported features individually rather than combining them into a single Action.
For example:
class VMManager {}
And:
async function getVM() {}
These should be treated as negative tests for the features identified as unsupported in the compatibility reference.
Testing note: Record the actual result, error message and appliance build for every test. The expected behaviour described here is based on the supplied compatibility matrix and should not be presented as an independently executed lab result until verified.
15. What Developers Should Watch Out For
Based on the capabilities and limitations discussed above, several practical considerations emerge.
15.1 Do not assume complete modern JavaScript support
The engine supports many modern features, but some syntax remains unsupported.
Always validate features such as classes, async/await, object rest and spread in function calls before using them in production Actions.
15.2 Do not treat Rhino as Node.js
Node.js modules, browser APIs and third-party JavaScript packages are not automatically available.
Use the APIs provided by Orchestrator and its installed integrations.
15.3 Be careful with Promise execution
Promise support does not mean that asynchronous callbacks will behave like they do in Node.js.
Do not return from an Action while relying on a Promise callback to populate its output.
15.4 Validate Java interoperability
Existing scripts may depend on Java classes, collections and methods.
Check the exact object type and available methods before assuming that a Java object behaves like a native JavaScript object.
15.5 Review system-level operations
Scripts that execute operating-system commands or depend on restricted Java runtime classes need specific attention.
Do not assume that JavaScript syntax support implies permission to perform operating-system operations.
15.6 Use Orchestrator logging
Use System.log() and System.error() rather than assuming that standard browser or Node.js logging APIs are available.
15.7 Test real Orchestrator objects
A standalone JavaScript test does not validate the behaviour of VMware inventory objects, plugin APIs or workflow execution.
Test the code in the actual Action or Scriptable Task context in which it will run.
16. Is the JavaScript Environment in 9.1.1 Good for Future Development?
The expanded JavaScript feature set is a positive step for Orchestrator development.
Developers can use a broader range of language capabilities to create cleaner and more maintainable automation code.
For example:
- Arrow functions simplify callback handling.
- Template literals improve dynamic string construction.
- Destructuring simplifies REST response processing.
- Map and Set provide more appropriate collection types.
- Optional chaining simplifies nested object access.
- Nullish coalescing provides cleaner default-value handling.
- Modern Array methods reduce repetitive loops and custom utility functions.
However, some limitations remain significant.
The absence of class syntax and async/await means developers cannot directly adopt every modern JavaScript programming pattern. Promise execution behaviour also requires care, and Java interoperability remains dependent on the Orchestrator execution environment and its security restrictions.
My recommendation is to use the supported modern JavaScript features where they improve clarity, but keep Actions predictable, self-contained and aligned with the APIs exposed by Orchestrator.
Avoid introducing complex asynchronous patterns or unnecessary dependencies on Java runtime internals.
The best approach is not to use every newly supported feature simply because it is available. It is to choose features that make automation easier to understand, maintain and troubleshoot.
17. Conclusion: So, Will My Existing Orchestrator Code Work in VCF Automation 9.1.1?
As a developer, I can understand your frustration after reading this entire article!
We discussed the Rhino engine, JavaScript features, unsupported syntax, Java interoperability, Promises, runtime behaviour and several other technical details.
But you might still be thinking:
“That’s all fine, but I have hundreds of existing Actions and workflows. Will my code work in VCF Automation 9.1.1 or not?” 😅
Fair question! Let’s look at it from a developer’s perspective.
The newer Rhino engine brings support for a much broader range of JavaScript features. However, the important question is whether the JavaScript code written for the existing Orchestrator environment continues to work in the 9.1.1 runtime.
My understanding is that standard JavaScript code should generally remain compatible, while the newer engine provides additional capabilities that developers can use when needed.
For example, if your existing Actions use variables, functions, loops, conditions, arrays, objects, JSON operations and standard JavaScript methods, there is no reason to rewrite that code simply because the engine has been upgraded.
However, we should also consider another important aspect of Orchestrator: Java interoperability.
Most Orchestrator plugins are Java-based integrations that expose their functionality through scripting objects. This means our automation code doesn’t interact only with JavaScript. It also interacts with Java-backed plugin objects, VMware APIs and other platform components.
Therefore, compatibility is not just about whether Rhino understands the JavaScript syntax. It is also about whether the underlying objects, methods and integrations continue to behave as expected.
So, what should you take away from this?
- Don’t rewrite everything: Existing standard JavaScript code should not need changes just because of the engine upgrade.
- Don’t assume everything is compatible: Code relying on specific Rhino behaviours, unsupported features or restricted Java APIs deserves attention.
- Understand the new capabilities: You can take advantage of modern JavaScript features when they make your Actions simpler and easier to maintain.
- Consider Java interoperability: Remember that Orchestrator plugins and their scripting objects are an important part of the execution environment.
- Keep your automation practical: New JavaScript features are opportunities, not mandatory changes to every existing Action.
Ultimately, the newer Rhino engine is a positive step for Orchestrator developers. It expands the JavaScript capabilities available to us, while the actual compatibility of existing automation depends on the code and platform APIs it uses.
So, before planning a complete rewrite of your Orchestrator codebase, take a breath! Your existing JavaScript code is not automatically incompatible with VCF Automation 9.1.1. Understand its dependencies, identify potential limitations and make changes only where they are actually required.
And remember, just because a new JavaScript feature is available doesn’t mean every existing Action needs to start using it! 😉
Happy Automating! 🚀
If you have any questions, thoughts, or just want to geek out over Aria Orchestrator/VCF Operations Orchestrator, feel free to ping me on LinkedIn or drop me an email at jagadeeshvuppala1996@gmail.com. I’m always happy to help!
Leave a Reply