Runtime
Contents
What powers your Enonic app at execution time - the JVM-hosted JavaScript engine, the module system, the threading model, and the cluster-aware machinery you get for free. Regardless of whether the app serves an API, a webapp or a site, this is the environment its code runs in.
Execution model
Enonic XP runs on top of the Java Virtual Machine. Your app’s TypeScript and JavaScript files are executed by an embedded JavaScript engine, with full access to the JVM through the Java bridge. There is no Node.js, no separate JS process, and no inter-process bridge — server-side JS runs inside the same JVM as the platform itself.
Two engines are available:
- Nashorn
-
The default engine. Stable, production-ready, ships with every XP install. All XP releases since 7.x have shipped Nashorn.
- GraalJS
-
The engine that will replace Nashorn, available as a preview since XP 8.1.0. Not for production. See GraalJS.
An app declares its engine in build.gradle; see Choosing an engine.
Language level
Every module runs in ECMAScript strict mode, on both engines.
Nashorn implements ECMAScript 5.1, with only a subset of later (ES2015+) features. Transpile to ES5 - that is what the official starters do. See TypeScript for the build step.
GraalJS implements the current ECMAScript standard. Code transpiled to ES5 runs on either engine, which makes it the portable choice while GraalJS is in preview.
Modules and bundling
Each .ts or .js file in your app is a module under the CommonJS module specification. Modules export values via exports, and consume other modules via require() — import and export statements are not supported at runtime, on either engine, see ECMAScript modules. Paths are rooted at the app bundle, not the filesystem — require('/lib/greet') loads src/main/resources/lib/greet.ts from the same app.
Platform libraries are referenced by name (for example, require('/lib/xp/content')). See Functions: Modules and the bundle for the full path-resolution rules and bundle boundaries.
TypeScript is supported as the authoring language; the build step transpiles to JavaScript before deployment. See TypeScript Definitions.
Loading and caching
XP loads each module once per app deploy and caches it. Subsequent require() calls and subsequent triggers reuse the cached module — the file is not re-read or re-parsed. Top-level code in a module therefore runs exactly once per deploy.
| On GraalJS a module is loaded once per script context rather than once per deploy, so its top-level code may run several times. See Script contexts. |
This makes module top-level a natural place for one-time setup (requiring libraries, computing constants), but a poor place for mutable state. Treat anything declared outside a function body as immutable after load. For state that must survive between invocations, use a repo node, the cache library, or another explicit persistence mechanism.
See Functions: Loading and caching for the implications on each function type.
Threading
The runtime is multithreaded. The platform serves requests, dispatches events, and runs background tasks concurrently across multiple threads, taking advantage of multi-core CPU architectures. There is no single event loop — each request, task, or event listener runs on a worker thread from a managed pool.
Two consequences for your code:
-
Module-level state is shared across threads. Cached modules are visible to every thread executing within the app. If two requests both call
require('/lib/cache'), they receive the same module instance. Treat top-level state as read-only. -
Implementations are reentrant. A function may be invoked concurrently with itself. Don’t accumulate state in module-level variables across invocations.
Clustering
XP can run as a single node or as a cluster of nodes sharing the same data. The runtime is cluster-aware out of the box:
-
Events fire across the cluster. A listener registered on any node receives events published from any other node. See Events.
-
Scheduled jobs are distributed. A scheduled job runs on one cluster node per fire — XP coordinates so the same job doesn’t execute twice. See Scheduler.
-
Repositories are replicated. Content writes propagate to all nodes; reads can target any node.
Apps deployed to a cluster are installed and started on every node. The runtime guarantees the same module-loading and caching semantics on each node — your app sees a consistent execution environment regardless of which node serves a given request.
What you get for free
Apps run inside a fully equipped platform. The runtime gives you, without extra setup:
-
Persistence — content, nodes, repositories, blobs, all queryable and clusterable.
-
HTTP routing — the Portal dispatches incoming requests to four services (
/api,/admin,/site,/webapp); you provide the implementations. -
Identity & access management — pluggable ID providers, role-based permissions, audit logging.
-
Cluster-wide events — see Events.
-
Background work — tasks and scheduled jobs.
-
Localization — see Localization.
-
Java interop — see Java bridge.
- Compatibility
-
An app built against any XP release runs unchanged on every patch and minor release within the same major version. Major version upgrades are documented separately — see Upgrading.
What’s in this section
This section documents the parts of an app that are not tied to a specific HTTP service or background mechanism. The sections above describe how your code executes; the pages below cover the building blocks in detail.
- JavaScript engines
-
Nashorn and GraalJS: how an app chooses its engine, and what differs between them.
- Apps
-
What an Enonic app is, how XP runs many of them at once, and the install/start/stop/redeploy lifecycle. Covers bundle isolation, cross-app composition, and configuration.
- Functions
-
How modules and exported functions form the building blocks of an app. Covers the CommonJS module system, the bundle boundary, module caching, and the framework contracts that bind specific exports to triggers (HTTP requests, events, scheduled times, lifecycle).
- Main
-
The
main.tslifecycle hook — code that runs when your app starts and stops. - Globals
-
Global objects and functions available in every module —
app,log,require,resolve, and friends. - Events
-
The cluster-wide pub/sub bus. Publish from anywhere — request handlers, tasks,
main.ts— and subscribe to platform events (content writes, repo updates) or your own custom domain events. - Localization
-
Localizing text in your app and the schemas it deploys, using
phrasesfiles and the localization API. - Java bridge
-
Calling JVM code from JavaScript via the
__global. Required reading for library authors and anyone integrating with existing Java codebases.
How the parts relate
The runtime is the foundation: a JVM-based execution environment with a CommonJS module system, multithreading, clustering, and a platform’s worth of services attached. Functions are the units of code the runtime invokes — most of what you write is regular module code, with a smaller subset bound to framework contracts. main.ts is the lifecycle contract. Events are the cluster-wide pub/sub primitive, available from any function. Globals, TypeScript, the Java bridge, and localization are tools you reach for inside any function, regardless of which contract called it.
For HTTP-bound contracts (request handlers, filters, error handlers, websockets, SSE, ID providers), see Web. For asynchronous background execution (tasks, scheduled jobs), see Background.