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 <[email protected]> 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`
