Switching the HTTP Engine (Grizzly to Jetty) deprecated
Since 8.52 the middleware ships two HTTP engines: Grizzly (the default) and Jetty (opt-in). The switch is a single property; ports, probes and the load balancer stay untouched, and rollback is just flipping the property back. This runbook walks through the switch — including the optional Grizzly WebSocket side-car for deployments that still run Grizzly-typed WebSocket applications.
TL;DR
com.openexchange.http.jetty.enabled=true
# only if you deploy Grizzly-typed WebSocket applications (e.g. OMF target, Documents RT2):
com.openexchange.http.grizzly.websocketSidecarPort=8011
Restart the middleware. Done — everything else below is preparation, verification and detail.
Step 1 — Check three things before you switch
- Is
open-xchange-jettyinstalled? In containerized deployments both engines are part of the core image — nothing to do. - Do you deploy Grizzly-typed WebSocket applications? These are bundles that register WebSocket applications through the
WebApplicationServiceAPI (in practice: OMF target, Documents RT2). If yes, you need the WebSocket side-car and one routing change. Applications using standardjakarta.websocketneed nothing. If you have no WebSocket applications at all, skip the side-car entirely. - Do you override one of the few Grizzly-only properties? Almost all tuning carries over automatically (see How configuration carries over). Only the properties in the Grizzly-only table are ignored by Jetty — check the table if you override any of them.
Do the first switch on a staging system and run your usual client checks against it.
Step 2 — Set the properties
Plain installation (e.g. in /opt/open-xchange/etc/):
com.openexchange.http.jetty.enabled=true
# only for the side-car:
com.openexchange.http.grizzly.websocketSidecarPort=8011
Kubernetes (core-mw chart):
core-mw:
properties:
com.openexchange.http.jetty.enabled: "true"
# only for the side-car:
com.openexchange.http.grizzly.websocketSidecarPort: "8011"
Pick a free port for the side-car — note that 8010 is already the default HTTPS connector port (com.openexchange.connector.networkSslListenerPort).
Step 3 — Route WebSocket paths (side-car only)
Skip this step if you do not use the side-car.
Clients are not affected: they keep connecting through the load balancer with unchanged URLs (wss://host/<path>). Only the routing behind the proxy changes — forward the WebSocket context paths of the affected applications to the side-car port. Everything else keeps going to the HTTP port (8009).
Kubernetes — expose the side-car port via the core-mw chart values. Both containerPorts and the per-role services[].ports are lists, and Helm replaces overridden lists wholesale instead of merging them — always repeat the existing HTTP entries, otherwise probes and the regular HTTP service break:
core-mw:
containerPorts:
- containerPort: 8009 # repeat the default — the list is replaced, not merged!
name: http
- containerPort: 8011
name: ws-sidecar
roles:
http-api: # the role whose service should expose the side-car
services:
- type: ClusterIP
ports:
- port: 80 # repeat the existing entry — same reason
targetPort: http
protocol: TCP
name: http
- port: 8011
targetPort: ws-sidecar
protocol: TCP
name: ws-sidecar
Then add a path rule to the ingress (the ingress is not part of the core-mw chart):
# Ingress: one rule per WebSocket context path
- path: /<websocket-context-path>
pathType: Prefix
backend:
service:
name: middleware-http-api
port:
number: 8011
Apache — one line per WebSocket context path (mod_proxy_wstunnel):
ProxyPass /<websocket-context-path> ws://localhost:8011/<websocket-context-path>
Step 4 — Restart and verify
Restart the middleware (in Kubernetes the property change triggers a rolling restart), then check:
- Log lines confirming the engines:
Starting Jetty server.
Started Jetty HTTP network listener on host: 0.0.0.0 and port: 8009
Without side-car additionally: Jetty HTTP engine is enabled. Skipping start-up of Grizzly server.
With side-car instead:
Jetty HTTP engine is enabled. Starting Grizzly in WebSocket side-car mode.
Started Grizzly WebSocket side-car listener on host: 0.0.0.0 and port: 8011
GET :8009/readyreturns200,GET :8016/livereturns200— probes unchanged.- Login and API calls work through the regular endpoint.
- With side-car: a WebSocket client of the affected application connects through the proxy with its unchanged URL.
Rollback
Set com.openexchange.http.jetty.enabled=false (or remove the property) and restart. Grizzly serves everything again, including WebSockets on the HTTP port. Side-car routing rules can stay (they are inert while the side-car port is closed) or be reverted along with it. Nothing else to undo.
The WebSocket side-car (optional)
On Jetty, WebSockets are provided through the standard jakarta.websocket (JSR 356) container. Applications written against the Grizzly-typed WebApplicationService API do not run on it without a code migration. The side-car bridges that gap: Grizzly starts in a WebSocket-only mode on the configured port — original Grizzly WebSocket stack, so the affected applications run completely unchanged. No servlet container, no Comet, no liveness; 0 (the default) disables it entirely.
Both WebSocket stacks run in parallel. A given WebSocket path is routed to exactly one port: Grizzly-typed applications to the side-car port, jakarta.websocket applications to the regular HTTP port (no extra routing needed). When an application gets migrated to jakarta.websocket, flip its path back to 8009; when the last one is migrated, remove the side-car property and the routing rules.
The side-car is a transitional bridge, not a permanent mode — it keeps the Grizzly stack in the deployment. Plan the migration of the affected applications regardless.
Reference
How configuration carries over
Nothing needs to be re-configured for a typical deployment:
- Engine-neutral namespaces (
com.openexchange.connector.*,com.openexchange.server.*,com.openexchange.servlet.*,com.openexchange.cookie.*) apply to both engines unchanged — ports, host, shutdown, cookies, forwarded headers, body limits. - Grizzly tuning falls through: when a
com.openexchange.http.jetty.*key is not set, its equally namedcom.openexchange.http.grizzly.*pendant applies. An explicitly set Jetty key always wins. See the property documentation for the full list.
Settings that only exist in Jetty
com.openexchange.http.jetty.enabled— the engine switch.com.openexchange.http.jetty.http2.enabled— cleartext HTTP/2 (h2c); Grizzly has no HTTP/2 support at all.com.openexchange.http.jetty.metrics.enabled— engine metrics via Micrometer (defaulttrue).com.openexchange.http.jetty.accesslog.retainDays— retention for rotated access logs.com.openexchange.http.jetty.selectorRunnersCount— deliberately not adopted from the Grizzly pendant; Jetty keeps its auto-sizing default.
Settings that only exist in Grizzly
These are ignored by the Jetty engine — relevant only if you override one of them:
| Property | Note |
|---|---|
hasCometEnabled | No Comet on Jetty; Drive long polling uses the engine-neutral servlet-async listener on both engines. |
writeTimeoutMillis | Jetty has a single idle timeout, fed from readTimeoutMillis, and it covers stalled writes as well — no protection is lost. Both Grizzly timeouts default to 60000, so only a deployment that sets them to different values sees a difference: on Jetty the read value applies to both directions. |
keepAlive, minWriteBufferSize | Transport internals without a Jetty equivalent. |
maxQueryStringSize | Inactive on Grizzly, too — the parameter check it feeds is disabled. On both engines the query string is bounded by maxHttpHeaderSize, which covers the request line and answers 414. |
sessionUnjoinedThreshold, removeNonAuthenticatedSessions | Grizzly-specific session eviction; Jetty sessions expire via inactivity only. |
supportHierachicalLookupOnNotFound | Retry-climb on 404 is not replicated; standard servlet longest-prefix matching applies. |
hasAccessLogEnabled, accesslog.statusThreshold, accesslog.synchronous, accesslog.timezone | On Jetty the access log is enabled by the presence of accesslog.file; these options have no pendant. accesslog.format values other than common and combined (e.g. agent, referer, the vhost_* variants) are served as combined. |
websocketSidecarPort | Grizzly-side by design — configures the side-car (only effective while Jetty is enabled). |
com.openexchange.servlet.maxActiveSessions (engine-neutral) is enforced on both engines. The eviction differs in detail: Grizzly evicts via its session cache, Jetty invalidates the least recently used among the oldest sessions once the cap is exceeded.
Troubleshooting
BindExceptionin the log: the side-car port collides with another listener. Verify it differs fromcom.openexchange.connector.networkListenerPort,...networkSslListenerPortand...livenessPort.- Side-car does not start although the port is set:
com.openexchange.http.grizzly.hasWebSocketsEnabledis set tofalse— the start-up is skipped with a warning. - WebSocket clients get HTTP 404 after the switch: the application's context path is still routed to the HTTP port. Check the ingress/
ProxyPassrule for the side-car port. - A Grizzly tuning seems lost: check the Grizzly-only table; everything else is adopted via the pendant fallback unless an explicit
com.openexchange.http.jetty.*override shadows it.