You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2026/07/16 18:22:00 UTC
(tinkerpop) 03/06: Reorder 4.0.0-beta.3 upgrade docs by significance
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 08f779d88366c1d67a37a4ea2cde2717a3964b0d
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Thu Jul 16 12:31:04 2026 -0400
Reorder 4.0.0-beta.3 upgrade docs by significance
Assisted-by: Claude Code:claude-opus-4-8
---
docs/src/upgrade/release-4.x.x.asciidoc | 456 +++++++++++++++-----------------
1 file changed, 219 insertions(+), 237 deletions(-)
diff --git a/docs/src/upgrade/release-4.x.x.asciidoc b/docs/src/upgrade/release-4.x.x.asciidoc
index 11c89536fc..8ef445cb7a 100644
--- a/docs/src/upgrade/release-4.x.x.asciidoc
+++ b/docs/src/upgrade/release-4.x.x.asciidoc
@@ -32,151 +32,6 @@ complete list of all the modifications that are part of this release.
=== Upgrading for Users
-==== Bindings are now Parameters
-
-The map of named values substituted into a Gremlin query was previously called "bindings" in some places and
-"parameters" in others. TinkerPop now refers to these consistently as *query parameters*, reserving "bindings" for
-the distinct concept of `ScriptEngine` variable bindings. As a result, the driver methods for supplying query
-parameters have been renamed. Update any code that sets them:
-
-- Java: `RequestMessage.Builder.addBindings(...)` is now `addParameters(...)`.
-- Python: `client.submit(..., bindings=...)` is now `submit(..., parameters=...)`, and the `request_options`
-key `bindings` is now `parameters`.
-- Go: `SetBindings`/`SetBindingsString`/`AddBinding` are now
-`SetParameters`/`SetParametersString`/`AddParameter`.
-- .NET: `AddBinding`/`AddBindings`/`AddBindingsString` are now
-`AddParameter`/`AddParameters`/`AddParametersString`.
-- JavaScript: `addBinding`/`addBindings`/`addBindingsString` are now
-`addParameter`/`addParameters`/`addParametersString`, and the `RequestOptions.bindings` field is now `parameters`.
-
-For example, in the Java driver:
-
-[source,java]
-----
-final Map<String, Object> params = new HashMap<>();
-params.put("x", 1);
-
-// 3.x
-RequestMessage.build("g.V(x)").addBindings(params).create();
-
-// 4.x
-RequestMessage.build("g.V(x)").addParameters(params).create();
-----
-
-See: link:https://issues.apache.org/jira/browse/TINKERPOP-3262[TINKERPOP-3262]
-
-==== Standardizing GLV Connection Options
-
-TinkerPop 4.x standardizes connection option names and defaults across all five Gremlin Language Variants (Java, Python,
-.NET, Go, and JavaScript). Each driver using its language-idiomatic casing (`camelCase`, `PascalCase`, or `snake_case`).
-These renames are breaking, the old option names have been removed.
-
-NOTE: Timeouts use a millisecond-suffixed canonical name (`connectTimeoutMillis`, `readTimeoutMillis`,
-`idleTimeoutMillis`, `keepAliveTimeMillis`, and the `_millis` form in Python). Java, Go, .NET, and Python also accept an
-idiomatic duration companion for the same setting (Java `Duration`, Go `time.Duration`, .NET `TimeSpan`, Python seconds);
-set only one form per option. JavaScript exposes only the millisecond form.
-
-===== Standardized options (cross-GLV)
-
-The table lists each standardized option by driver. Defaults are shown in parentheses; "n/a" means the driver does not
-expose that option.
-
-[width="100%",cols="2,2,2,2,2,2",options="header"]
-|=========================================================
-|Concept |Java (`Cluster.Builder`) |Python (kwarg) |.NET (`ConnectionSettings`) |Go (settings field) |JavaScript (option)
-|Max pooled connections |`maxConnections` (128) |`max_connections` (128) |`MaxConnections` (128) |`MaxConnections` (128) |`maxConnections` (128)
-|Connect timeout |`connectTimeoutMillis` (5000) |`connect_timeout_millis` (5000) |`ConnectTimeoutMillis` (5000) |`ConnectTimeoutMillis` (5000) |n/a
-|Idle-read timeout |`readTimeoutMillis` (0/off) |`read_timeout_millis` (off) |`ReadTimeoutMillis` (0/off) |`ReadTimeoutMillis` (0/off) |`readTimeoutMillis` (undici default)
-|Pool idle timeout |`idleTimeoutMillis` (180000) |`idle_timeout_millis` (180000) |`IdleTimeout` (180s) |`IdleTimeoutMillis` (180000) |n/a
-|TCP keep-alive idle |`keepAliveTimeMillis` (30000) |`keep_alive_time_millis` (30000) |`KeepAliveTime` (30s) |`KeepAliveTimeMillis` (30000) |`keepAliveTimeMillis` (30000)
-|Compression |`compression` (`DEFLATE`) |`compression` (`'deflate'`) |`Compression` (`Deflate`) |`Compression` (`CompressionDeflate`) |`compression` (`'deflate'`)
-|Connection-level batch size |`batchSize` (64) |`batch_size` (64) |`BatchSize` (64) |`BatchSize` (64) |`batchSize` (64)
-|Connection-level bulkResults |`bulkResults` (false) |`bulk_results` (false) |`BulkResults` (false) |`BulkResults` (false) |`bulkResults` (false)
-|Max response header bytes |`maxResponseHeaderBytes` (8192) |n/a |`MaxResponseHeaderBytes` (handler default) |`MaxResponseHeaderBytes` (net/http default) |`maxResponseHeaderBytes` (undici default)
-|TLS configuration |`ssl(SslContext)` + keystore builders |`ssl` (`SSLContext`) |`Ssl` (`SslClientAuthenticationOptions`) |`Ssl` (`*tls.Config`) |runtime (`NODE_EXTRA_CA_CERTS`, etc.)
-|HTTP proxy |`proxy(ProxyOptions)` |`proxy` |`Proxy` (`IWebProxy`) |`Proxy` (env default) |`proxy` (undici `ProxyAgent`)
-|Request interceptors |`interceptors` |`interceptors` |interceptor delegates |`Interceptors` |`interceptors`
-|=========================================================
-
-===== Behavior changes (all drivers)
-
-These change runtime behavior on upgrade even if you do not change your configuration:
-
-- *Compression defaults to on.* Every driver now defaults compression to `deflate` and sends `Accept-Encoding: deflate`.
- Disable it with the compression option's "none" value (`Compression.NONE`, `'none'`, `Compression.None`,
- `CompressionNone`).
-- *Connect timeout lowered to 5s* (from 15s, where applicable) and is now actually applied to transport establishment
- (TCP connect plus TLS handshake), not the whole request.
-- *`readTimeout` is an idle-read timeout*, reset on each inbound response chunk, so it is streaming-safe and bounds only
- the gap between chunks, not total response duration. It is off by default.
-- *`idleTimeout` reaps only pooled connections* that are idle between requests; it no longer bounds an in-flight
- response (that is `readTimeout`'s job).
-
-===== Driver-specific notes
-
-- *Java* (`gremlin-driver`): Renamed: `maxConnectionPoolSize`->`maxConnections`, `connectionSetupTimeoutMillis`->`connectTimeoutMillis`,
- `idleConnectionTimeoutMillis`->`idleTimeoutMillis`, `resultIterationBatchSize`->`batchSize`, `serializer`->`responseSerializer`,
- and `RequestOptions` `addG`->`traversalSource`. New: `readTimeoutMillis`, `keepAliveTimeMillis`,
- `maxResponseHeaderBytes`, `proxy(ProxyOptions)`, `url(String)`, `ssl(SslContext)`. Removed: `maxResponseContentLength`.
- `validationRequest` default reconciled to `g.inject(0)`.
-- *Python* (`gremlin-python`): Renamed: `pool_size`->`max_connections` (default 8->128) and `ssl_options`->`ssl`.
- New: `connect_timeout_millis`, `read_timeout_millis`, `idle_timeout_millis`, `keep_alive_time_millis`, `compression`,
- `batch_size`, `proxy`, `trust_env`. `auth.sigv4` gained an optional credentials provider. Removed: `headers` (use interceptors)
- and `max_content_length`.
-- *.NET* (`gremlin-dotnet`): Renamed: `MaxConnectionsPerServer`->`MaxConnections`, `ConnectionTimeout`->`ConnectTimeout`,
- `IdleConnectionTimeout`->`IdleTimeout`, `KeepAliveInterval`->`KeepAliveTime`, `EnableCompression`->`Compression`, and
- `Auth.BasicAuth`/`Auth.SigV4Auth`->`Auth.Basic`/`Auth.Sigv4`. New: `ReadTimeout`, `MaxResponseHeaderBytes`,
- `Proxy`, `Ssl`, `BulkResults`.
-- *Go* (`gremlin-go`): Renamed: `MaximumConcurrentConnections`->`MaxConnections`, `IdleConnectionTimeout`->`IdleTimeout`,
- `KeepAliveInterval`->`KeepAliveTime`, `ConnectionTimeout`->`ConnectTimeout`, `TlsConfig`->`Ssl`, `RequestInterceptors`->`Interceptors`,
- `EnableCompression`->`Compression`. Auth helpers moved from package `gremlingo`
- (`BasicAuth`/`SigV4Auth`/`SigV4AuthWithCredentials`) into a new `auth` sub-package (`auth.Basic`/`auth.SigV4`/`auth.SigV4WithCredentials`).
- New: `ReadTimeout(Millis)`, `MaxResponseHeaderBytes`, `Proxy` (defaults to `http.ProxyFromEnvironment`), `BatchSize`, `BulkResults`.
-- *JavaScript* (`gremlin-javascript`): adopted `undici` as a pinned dependency providing the default dispatcher built from the
- options above. Renamed: `reader`->`responseSerializer`. New: `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`,
- `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`. Removed `headers` (use interceptors) and the
- `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` options (TLS is configured through the Node/undici runtime). undici is swapped
- out in browser bundles, where these connection-pool options are managed by the browser.
-
-See: link:https://lists.apache.org/thread/yqtr2wnb1kq2pqqq4002cz511q5o0bkg[[DISCUSS] Standardizing GLV connection options in TinkerPop 4].
-
-==== Renaming `evaluationTimeout` to `timeoutMillis`
-
-The per-request execution timeout is now referred to by a single name, `timeoutMillis`, everywhere. `timeoutMillis` is
-the maximum time in milliseconds that a request is allowed to execute on the server before it times out. It can be
-configured server-wide and overridden on a per-request basis. Previously the same concept was called
-`evaluationTimeout` in the server configuration, the `with()` script token, and several driver APIs. The `Millis`
-suffix aligns it with the driver connection options such as `connectTimeoutMillis` and `readTimeoutMillis`.
-
-This is a breaking change with no backward-compatible alias. The old `evaluationTimeout` name (and the long-deprecated
-`scriptEvaluationTimeout`) are no longer recognized anywhere. Update each surface as follows:
-
-- *Server config*: the `gremlin-server.yaml` key `evaluationTimeout` becomes `timeoutMillis` (default still 30000).
-- *Script token*: `g.with('evaluationTimeout', 500)` becomes `g.with('timeoutMillis', 500)`.
-- *Java driver*: `RequestOptions.Builder.timeout(long)` becomes `timeoutMillis(long)` and `getTimeout()` becomes `getTimeoutMillis()`.
-- *Go driver*: `RequestOptionsBuilder.SetEvaluationTimeout(int)` becomes `SetTimeoutMillis(int)`.
-- *.NET driver*: `Tokens.ArgsEvalTimeout` becomes `Tokens.ArgsTimeoutMillis` and `RequestMessage.Builder.AddEvaluationTimeout(...)`
- becomes `AddTimeoutMillis(...)`.
-- *JavaScript driver*: the request option `{ evaluationTimeout: N }` becomes `{ timeoutMillis: N }`.
-- *Python driver*: use the token `timeoutMillis` (e.g. `g.with_('timeoutMillis', 500)` or `request_options={'timeoutMillis': 500}`).
-
-Driver and server should be upgraded together. A driver sending the old `evaluationTimeout` field to a 4.x server has
-that field silently ignored and falls back to the server's default timeout, as with any unrecognized request argument.
-
-==== Java Runtime Upgrade
-
-TinkerPop 4.0 raises the minimum Java version from 11 to 17 for both building and running, and adds support for running
-on Java 21 and Java 25. Supporting Java 25 required upgrading Groovy to 4.0.32, Hadoop to 3.4.3, Spark to 4.1.x, and
-Netty to 4.2.x. These upgrades collectively prohibit building on Java 11, but enable support in Java 21 and 25.
-
-As with the earlier JDK 17 support, some libraries still rely on deep reflection (most notably the Kryo serialization
-library used with OLAP), so it may be necessary to `--add-opens` or `--add-exports` certain modules at runtime. The set
-of options used by TinkerPop's own tests is unchanged.
-
-==== .NET Runtime Upgrade
-
-The minimum target framework is now `net8.0` (previously `netstandard2.0;net6.0`).
-
==== Declarative Pattern Matching
Gremlin has always offered both imperative and declarative styles to writing graph queries. While the imperative style
@@ -235,46 +90,14 @@ TinkerGraph uses it out of the box with no configuration required. Graph provide
can add the `gql-gremlin` dependency and register `GqlDeclarativeMatchStrategy` as described in the
link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tinkerpop-providers-tinkergql[provider documentation].
-==== Request Interceptors
-
-When TinkerPop supported WebSockets prior to 4.0.0, the Java driver offered a `RequestInterceptor` interface (and
-its predecessor, `HandshakeInterceptor`) that allowed modification of the raw Netty `FullHttpRequest`. For WebSocket
-connections, the interceptor only ran on the initial HTTP upgrade handshake. With the move to HTTP, the notion of
-the "interceptor" has shifted to a per-request concern that is now standardized across all GLVs.
-
-All GLVs now support request interceptors, which allow modification of the HTTP request before it is sent to the
-server. An interceptor is a function that receives the mutable HTTP request object and can modify headers, the
-request body, the URI, or the HTTP method. Interceptors are run in the order they are registered.
-
-The most common use case for interceptors is authentication (e.g., SigV4 signing), but they can also be used to add
-provider-specific fields, inject custom headers, or transform the request body.
-
-Here is a simple Java example that adds a custom header:
-
-[source,java]
-----
-Cluster cluster = Cluster.build("localhost")
- .interceptors(request -> request.headers().put("X-Custom-Header", "value"))
- .create();
-----
-
-Authentication is also an interceptor. Each GLV provides convenience methods (e.g., `Auth.basic()`, `Auth.sigv4()`)
-that return interceptors and can be registered alongside custom ones.
-
-For full details on the interceptor API for each language variant, refer to the RequestInterceptor section in
-each GLV's documentation in the
-link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants]
-reference.
-
==== Multi-Label Support
Until now, vertices in the property graph model were limited to a single, immutable label assigned at creation. This
release introduces configurable label cardinality, allowing vertices to carry multiple labels that can be added and
-removed over their lifetime.
-
-The feature is controlled by `LabelCardinality`, a graph-level setting that defaults to `ONE`, preserving the
-existing single-label behavior. To enable multi-label in TinkerGraph, set the vertex label cardinality in the graph
-properties:
+removed over their lifetime. This is an optional feature for graph providers and not all will support it in the same
+way. TinkerGraph offers a configurable approach to enabling the the feature using one of the `LabelCardinality` settings
+as a graph-level setting that defaults to `ONE`, preserving the existing single-label behavior. To enable multi-label
+in TinkerGraph, set the vertex label cardinality in the graph properties:
```
gremlin.tinkergraph.vertexLabelCardinality=ZERO_OR_MORE
@@ -283,7 +106,7 @@ gremlin.tinkergraph.vertexLabelCardinality=ZERO_OR_MORE
The three modes are `ONE` (single, immutable, the 3.x default), `ONE_OR_MORE` (mutable, minimum one), and
`ZERO_OR_MORE` (fully flexible). Edge labels remain fixed at `ONE`.
-With multi-label enabled, several new traversal steps become available:
+The following examples demonstrate how Gremlin behaves with multi-label enabled:
```text
gremlin> g.addV('person','employee').property('name','marko')
@@ -334,49 +157,6 @@ gremlin> gml.V().has('name','marko').valueMap(true)
==>{id=0, label=[manager, person, employee], name=[marko]}
```
-Note the following behavioral details:
-
-* The deprecated `label()` step returns a single, non-deterministic label when multiple labels are present. Always
- use `labels()` to retrieve the full set reliably.
-* When a source is configured with `with("multilabel")`, the label output can be forced back to a single string
- per-traversal using `with("singlelabel")`. This is useful for providers that enable multi-label output by default
- but need an escape hatch for backward compatibility. Note that when both options are present on the same source,
- `"singlelabel"` always takes precedence regardless of the order in which they were applied.
-* Adding a label that already exists on the vertex is a no-op (when multi-label is enabled). Dropping a label that
- does not exist is also a no-op. With cardinality `ONE`, both `addLabel()` and `dropLabel()` throw since labels are
- immutable.
-* `hasLabel()` tests the predicate against each label on the element and returns `true` if at least one label
- satisfies it. For multi-label vertices, this means `hasLabel(P.neq("person"))` returns `true` when the vertex also
- carries other labels, a natural consequence of testing a negation predicate against a set of labels, where the
- non-matching labels will satisfy `neq`. To exclude vertices that carry a specific label, use the negation pattern:
- `g.V().not(__.hasLabel("person"))`. For single-label vertices the behavior is unchanged.
-
-==== Inconfigurable Request Serialization
-
-TinkerPop 3.x drivers used a single serializer configuration (for example, `serializer` in the Java driver or
-`message_serializer` in Python) that controlled both how a `RequestMessage` was serialized on the way to the server
-and how responses were deserialized on the way back. TinkerPop 4 splits these concerns. Requests are now always
-serialized as JSON (`application/json`) and that behavior is not configurable. The remaining serializer configuration
-is named `responseSerializer` (cased idiomatically per language) and controls only the `Accept` header and response
-deserialization.
-
-Applications that require a different request body encoding, such as GraphBinary for a server that expects it, can
-register a request interceptor that serializes the `RequestMessage` and replaces the body and `Content-Type` header.
-See the RequestInterceptor section for each GLV in the
-link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants]
-reference.
-
-See: link:https://issues.apache.org/jira/browse/TINKERPOP-3250[TINKERPOP-3250]
-
-==== Gremlator
-
-link:https://gremlator.com[Gremlator] has been rebuilt entirely in JavaScript as a browser-based single-page
-application and is now an official part of the Apache TinkerPop project. It translates Gremlin queries into
-equivalent representations in all supported language variants: Groovy, Java, Python, JavaScript, Go, .NET, and an
-anonymized form. The original gremlator.com was a prototype built by TinkerPop committer Dave Bechberger; the
-previous implementation required Java and a running Gremlin Server, whereas the new version runs entirely in the
-browser with no server infrastructure needed.
-
==== Transactions
TinkerPop 4.0 brings a set of related transaction changes across the drivers and embedded graphs: explicit remote
@@ -454,6 +234,220 @@ partial work is discarded if the user forgets to call `commit()`. In Java (both
can still be overridden via `tx.onClose(Transaction.CLOSE_BEHAVIOR.COMMIT)`. The non-Java GLVs do not support
configuring close behavior and always rollback.
+==== Subgraph Support in GLVs
+
+All GLVs now support the `subgraph()` step. Previously, calling `subgraph()` from a GLV produced an unknown-type error
+because the variant could not interpret the `Graph` payload that the server returned. Applications can now extract a
+portion of a remote graph as part of a normal traversal and inspect its vertices and edges directly from the client,
+without having to re-issue queries to reconstruct the result. See: <<subgraph-step>>.
+
+In the GLVs, the result is a detached snapshot of the captured vertices and edges, not a traversable `Graph` instance.
+It cannot be passed to `traversal().with(...)`, and mutating its collections has no effect on the source graph. To
+re-query elements against the original graph, extract their ids and call `g.V(id)` or `g.E(id)` against the original
+`GraphTraversalSource`.
+
+==== Gremlator
+
+link:https://gremlator.com[Gremlator] has been rebuilt entirely in JavaScript as a browser-based single-page
+application and is now an official part of the Apache TinkerPop project. It translates Gremlin queries into
+equivalent representations in all supported language variants: Groovy, Java, Python, JavaScript, Go, .NET, and an
+anonymized form. The original gremlator.com was a prototype built by TinkerPop committer Dave Bechberger; the
+previous implementation required Java and a running Gremlin Server, whereas the new version runs entirely in the
+browser with no server infrastructure needed.
+
+==== Bindings are now Parameters
+
+The map of named values substituted into a Gremlin query was previously called "bindings" in some places and
+"parameters" in others. TinkerPop now refers to these consistently as *query parameters*, reserving "bindings" for
+the distinct concept of `ScriptEngine` variable bindings. As a result, the driver methods for supplying query
+parameters have been renamed. Update any code that sets them:
+
+- Java: `RequestMessage.Builder.addBindings(...)` is now `addParameters(...)`.
+- Python: `client.submit(..., bindings=...)` is now `submit(..., parameters=...)`, and the `request_options`
+key `bindings` is now `parameters`.
+- Go: `SetBindings`/`SetBindingsString`/`AddBinding` are now
+`SetParameters`/`SetParametersString`/`AddParameter`.
+- .NET: `AddBinding`/`AddBindings`/`AddBindingsString` are now
+`AddParameter`/`AddParameters`/`AddParametersString`.
+- JavaScript: `addBinding`/`addBindings`/`addBindingsString` are now
+`addParameter`/`addParameters`/`addParametersString`, and the `RequestOptions.bindings` field is now `parameters`.
+
+For example, in the Java driver:
+
+[source,java]
+----
+final Map<String, Object> params = new HashMap<>();
+params.put("x", 1);
+
+// 3.x
+RequestMessage.build("g.V(x)").addBindings(params).create();
+
+// 4.x
+RequestMessage.build("g.V(x)").addParameters(params).create();
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-3262[TINKERPOP-3262]
+
+==== Standardizing GLV Connection Options
+
+TinkerPop 4.x standardizes connection option names and defaults across all five Gremlin Language Variants (Java, Python,
+.NET, Go, and JavaScript). Each driver using its language-idiomatic casing (`camelCase`, `PascalCase`, or `snake_case`).
+These renames are breaking, the old option names have been removed.
+
+NOTE: Timeouts use a millisecond-suffixed canonical name (`connectTimeoutMillis`, `readTimeoutMillis`,
+`idleTimeoutMillis`, `keepAliveTimeMillis`, and the `_millis` form in Python). Java, Go, .NET, and Python also accept an
+idiomatic duration companion for the same setting (Java `Duration`, Go `time.Duration`, .NET `TimeSpan`, Python seconds);
+set only one form per option. JavaScript exposes only the millisecond form.
+
+===== Standardized options (cross-GLV)
+
+The table lists each standardized option by driver. Defaults are shown in parentheses; "n/a" means the driver does not
+expose that option.
+
+[width="100%",cols="2,2,2,2,2,2",options="header"]
+|=========================================================
+|Concept |Java (`Cluster.Builder`) |Python (kwarg) |.NET (`ConnectionSettings`) |Go (settings field) |JavaScript (option)
+|Max pooled connections |`maxConnections` (128) |`max_connections` (128) |`MaxConnections` (128) |`MaxConnections` (128) |`maxConnections` (128)
+|Connect timeout |`connectTimeoutMillis` (5000) |`connect_timeout_millis` (5000) |`ConnectTimeoutMillis` (5000) |`ConnectTimeoutMillis` (5000) |n/a
+|Idle-read timeout |`readTimeoutMillis` (0/off) |`read_timeout_millis` (off) |`ReadTimeoutMillis` (0/off) |`ReadTimeoutMillis` (0/off) |`readTimeoutMillis` (undici default)
+|Pool idle timeout |`idleTimeoutMillis` (180000) |`idle_timeout_millis` (180000) |`IdleTimeout` (180s) |`IdleTimeoutMillis` (180000) |n/a
+|TCP keep-alive idle |`keepAliveTimeMillis` (30000) |`keep_alive_time_millis` (30000) |`KeepAliveTime` (30s) |`KeepAliveTimeMillis` (30000) |`keepAliveTimeMillis` (30000)
+|Compression |`compression` (`DEFLATE`) |`compression` (`'deflate'`) |`Compression` (`Deflate`) |`Compression` (`CompressionDeflate`) |`compression` (`'deflate'`)
+|Connection-level batch size |`batchSize` (64) |`batch_size` (64) |`BatchSize` (64) |`BatchSize` (64) |`batchSize` (64)
+|Connection-level bulkResults |`bulkResults` (false) |`bulk_results` (false) |`BulkResults` (false) |`BulkResults` (false) |`bulkResults` (false)
+|Max response header bytes |`maxResponseHeaderBytes` (8192) |n/a |`MaxResponseHeaderBytes` (handler default) |`MaxResponseHeaderBytes` (net/http default) |`maxResponseHeaderBytes` (undici default)
+|TLS configuration |`ssl(SslContext)` + keystore builders |`ssl` (`SSLContext`) |`Ssl` (`SslClientAuthenticationOptions`) |`Ssl` (`*tls.Config`) |runtime (`NODE_EXTRA_CA_CERTS`, etc.)
+|HTTP proxy |`proxy(ProxyOptions)` |`proxy` |`Proxy` (`IWebProxy`) |`Proxy` (env default) |`proxy` (undici `ProxyAgent`)
+|Request interceptors |`interceptors` |`interceptors` |interceptor delegates |`Interceptors` |`interceptors`
+|=========================================================
+
+===== Behavior changes (all drivers)
+
+These change runtime behavior on upgrade even if you do not change your configuration:
+
+- *Compression defaults to on.* Every driver now defaults compression to `deflate` and sends `Accept-Encoding: deflate`.
+ Disable it with the compression option's "none" value (`Compression.NONE`, `'none'`, `Compression.None`,
+ `CompressionNone`).
+- *Connect timeout lowered to 5s* (from 15s, where applicable) and is now actually applied to transport establishment
+ (TCP connect plus TLS handshake), not the whole request.
+- *`readTimeout` is an idle-read timeout*, reset on each inbound response chunk, so it is streaming-safe and bounds only
+ the gap between chunks, not total response duration. It is off by default.
+- *`idleTimeout` reaps only pooled connections* that are idle between requests; it no longer bounds an in-flight
+ response (that is `readTimeout`'s job).
+
+===== Driver-specific notes
+
+- *Java* (`gremlin-driver`): Renamed: `maxConnectionPoolSize`->`maxConnections`, `connectionSetupTimeoutMillis`->`connectTimeoutMillis`,
+ `idleConnectionTimeoutMillis`->`idleTimeoutMillis`, `resultIterationBatchSize`->`batchSize`, `serializer`->`responseSerializer`,
+ and `RequestOptions` `addG`->`traversalSource`. New: `readTimeoutMillis`, `keepAliveTimeMillis`,
+ `maxResponseHeaderBytes`, `proxy(ProxyOptions)`, `url(String)`, `ssl(SslContext)`. Removed: `maxResponseContentLength`.
+ `validationRequest` default reconciled to `g.inject(0)`.
+- *Python* (`gremlin-python`): Renamed: `pool_size`->`max_connections` (default 8->128) and `ssl_options`->`ssl`.
+ New: `connect_timeout_millis`, `read_timeout_millis`, `idle_timeout_millis`, `keep_alive_time_millis`, `compression`,
+ `batch_size`, `proxy`, `trust_env`. `auth.sigv4` gained an optional credentials provider. Removed: `headers` (use interceptors)
+ and `max_content_length`.
+- *.NET* (`gremlin-dotnet`): Renamed: `MaxConnectionsPerServer`->`MaxConnections`, `ConnectionTimeout`->`ConnectTimeout`,
+ `IdleConnectionTimeout`->`IdleTimeout`, `KeepAliveInterval`->`KeepAliveTime`, `EnableCompression`->`Compression`, and
+ `Auth.BasicAuth`/`Auth.SigV4Auth`->`Auth.Basic`/`Auth.Sigv4`. New: `ReadTimeout`, `MaxResponseHeaderBytes`,
+ `Proxy`, `Ssl`, `BulkResults`.
+- *Go* (`gremlin-go`): Renamed: `MaximumConcurrentConnections`->`MaxConnections`, `IdleConnectionTimeout`->`IdleTimeout`,
+ `KeepAliveInterval`->`KeepAliveTime`, `ConnectionTimeout`->`ConnectTimeout`, `TlsConfig`->`Ssl`, `RequestInterceptors`->`Interceptors`,
+ `EnableCompression`->`Compression`. Auth helpers moved from package `gremlingo`
+ (`BasicAuth`/`SigV4Auth`/`SigV4AuthWithCredentials`) into a new `auth` sub-package (`auth.Basic`/`auth.SigV4`/`auth.SigV4WithCredentials`).
+ New: `ReadTimeout(Millis)`, `MaxResponseHeaderBytes`, `Proxy` (defaults to `http.ProxyFromEnvironment`), `BatchSize`, `BulkResults`.
+- *JavaScript* (`gremlin-javascript`): adopted `undici` as a pinned dependency providing the default dispatcher built from the
+ options above. Renamed: `reader`->`responseSerializer`. New: `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`,
+ `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`. Removed `headers` (use interceptors) and the
+ `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` options (TLS is configured through the Node/undici runtime). undici is swapped
+ out in browser bundles, where these connection-pool options are managed by the browser.
+
+See: link:https://lists.apache.org/thread/yqtr2wnb1kq2pqqq4002cz511q5o0bkg[[DISCUSS] Standardizing GLV connection options in TinkerPop 4].
+
+==== Renaming `evaluationTimeout` to `timeoutMillis`
+
+The per-request execution timeout is now referred to by a single name, `timeoutMillis`, everywhere. `timeoutMillis` is
+the maximum time in milliseconds that a request is allowed to execute on the server before it times out. It can be
+configured server-wide and overridden on a per-request basis. Previously the same concept was called
+`evaluationTimeout` in the server configuration, the `with()` script token, and several driver APIs. The `Millis`
+suffix aligns it with the driver connection options such as `connectTimeoutMillis` and `readTimeoutMillis`.
+
+This is a breaking change with no backward-compatible alias. The old `evaluationTimeout` name (and the long-deprecated
+`scriptEvaluationTimeout`) are no longer recognized anywhere. Update each surface as follows:
+
+- *Server config*: the `gremlin-server.yaml` key `evaluationTimeout` becomes `timeoutMillis` (default still 30000).
+- *Script token*: `g.with('evaluationTimeout', 500)` becomes `g.with('timeoutMillis', 500)`.
+- *Java driver*: `RequestOptions.Builder.timeout(long)` becomes `timeoutMillis(long)` and `getTimeout()` becomes `getTimeoutMillis()`.
+- *Go driver*: `RequestOptionsBuilder.SetEvaluationTimeout(int)` becomes `SetTimeoutMillis(int)`.
+- *.NET driver*: `Tokens.ArgsEvalTimeout` becomes `Tokens.ArgsTimeoutMillis` and `RequestMessage.Builder.AddEvaluationTimeout(...)`
+ becomes `AddTimeoutMillis(...)`.
+- *JavaScript driver*: the request option `{ evaluationTimeout: N }` becomes `{ timeoutMillis: N }`.
+- *Python driver*: use the token `timeoutMillis` (e.g. `g.with_('timeoutMillis', 500)` or `request_options={'timeoutMillis': 500}`).
+
+Driver and server should be upgraded together. A driver sending the old `evaluationTimeout` field to a 4.x server has
+that field silently ignored and falls back to the server's default timeout, as with any unrecognized request argument.
+
+==== Java Runtime Upgrade
+
+TinkerPop 4.0 raises the minimum Java version from 11 to 17 for both building and running, and adds support for running
+on Java 21 and Java 25. Supporting Java 25 required upgrading Groovy to 4.0.32, Hadoop to 3.4.3, Spark to 4.1.x, and
+Netty to 4.2.x. These upgrades collectively prohibit building on Java 11, but enable support in Java 21 and 25.
+
+As with the earlier JDK 17 support, some libraries still rely on deep reflection (most notably the Kryo serialization
+library used with OLAP), so it may be necessary to `--add-opens` or `--add-exports` certain modules at runtime. The set
+of options used by TinkerPop's own tests is unchanged.
+
+==== .NET Runtime Upgrade
+
+The minimum target framework is now `net8.0` (previously `netstandard2.0;net6.0`).
+
+==== Request Interceptors
+
+When TinkerPop supported WebSockets prior to 4.0.0, the Java driver offered a `RequestInterceptor` interface (and
+its predecessor, `HandshakeInterceptor`) that allowed modification of the raw Netty `FullHttpRequest`. For WebSocket
+connections, the interceptor only ran on the initial HTTP upgrade handshake. With the move to HTTP, the notion of
+the "interceptor" has shifted to a per-request concern that is now standardized across all GLVs.
+
+All GLVs now support request interceptors, which allow modification of the HTTP request before it is sent to the
+server. An interceptor is a function that receives the mutable HTTP request object and can modify headers, the
+request body, the URI, or the HTTP method. Interceptors are run in the order they are registered.
+
+The most common use case for interceptors is authentication (e.g., SigV4 signing), but they can also be used to add
+provider-specific fields, inject custom headers, or transform the request body.
+
+Here is a simple Java example that adds a custom header:
+
+[source,java]
+----
+Cluster cluster = Cluster.build("localhost")
+ .interceptors(request -> request.headers().put("X-Custom-Header", "value"))
+ .create();
+----
+
+Authentication is also an interceptor. Each GLV provides convenience methods (e.g., `Auth.basic()`, `Auth.sigv4()`)
+that return interceptors and can be registered alongside custom ones.
+
+For full details on the interceptor API for each language variant, refer to the RequestInterceptor section in
+each GLV's documentation in the
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants]
+reference.
+
+==== Inconfigurable Request Serialization
+
+TinkerPop 3.x drivers used a single serializer configuration (for example, `serializer` in the Java driver or
+`message_serializer` in Python) that controlled both how a `RequestMessage` was serialized on the way to the server
+and how responses were deserialized on the way back. TinkerPop 4 splits these concerns. Requests are now always
+serialized as JSON (`application/json`) and that behavior is not configurable. The remaining serializer configuration
+is named `responseSerializer` (cased idiomatically per language) and controls only the `Accept` header and response
+deserialization.
+
+Applications that require a different request body encoding, such as GraphBinary for a server that expects it, can
+register a request interceptor that serializes the `RequestMessage` and replaces the body and `Content-Type` header.
+See the RequestInterceptor section for each GLV in the
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants]
+reference.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-3250[TINKERPOP-3250]
+
==== Expanding Dynamic Arguments to Additional Steps
Prior to 4.0, comparing a traverser's value against a dynamically computed reference required several coordinated
@@ -948,18 +942,6 @@ unwrap(toInt(29)); // 29
unwrap('hello'); // 'hello'
----
-==== Subgraph Support in GLVs
-
-All GLVs now support the `subgraph()` step. Previously, calling `subgraph()` from a GLV produced an unknown-type error
-because the variant could not interpret the `Graph` payload that the server returned. Applications can now extract a
-portion of a remote graph as part of a normal traversal and inspect its vertices and edges directly from the client,
-without having to re-issue queries to reconstruct the result. See: <<subgraph-step>>.
-
-In the GLVs, the result is a detached snapshot of the captured vertices and edges, not a traversable `Graph` instance.
-It cannot be passed to `traversal().with(...)`, and mutating its collections has no effect on the source graph. To
-re-query elements against the original graph, extract their ids and call `g.V(id)` or `g.E(id)` against the original
-`GraphTraversalSource`.
-
==== Provider Defined Types
Graph providers may now expose custom types as Provider Defined Types (PDT) (replacing the old `CustomTypeSerializer`