You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by co...@apache.org on 2026/06/11 04:26:37 UTC

(tinkerpop) branch PDT updated (c147094125 -> 2e711729bd)

This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a change to branch PDT
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


    from c147094125 Enable annotation-based PDT round-trip hydration in Java and .NET
     new c239ee6a2f Address PDT PR review: validation, docs, precedence tests, cleanup
     new 2e711729bd Address PDT PR review: SPI auto-wiring, API renames, test relocation

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 docs/src/dev/provider/index.asciidoc               |  52 ++++-
 docs/src/reference/gremlin-variants.asciidoc       |  34 +--
 docs/src/upgrade/release-4.x.x.asciidoc            |  15 +-
 .../gremlin/process/traversal/GremlinLang.java     |   6 +-
 .../gremlin/structure/io/binary/GraphBinaryIo.java |  60 ------
 .../types/ProviderDefinedTypeSerializer.java       |  12 +-
 .../io/graphson/PdtGraphSONSerializersV4.java      |   2 +-
 .../structure/io/pdt/ProviderDefinedType.java      |  31 +--
 .../io/pdt/ProviderDefinedTypeAdapter.java         |   6 +-
 .../io/pdt/ProviderDefinedTypeRegistry.java        |  16 +-
 .../grammar/GeneralLiteralVisitorTest.java         |   6 +-
 .../gremlin/process/traversal/GremlinLangTest.java |  42 ++++
 .../gremlin/structure/io/binary/HeapBuffer.java    | 230 ---------------------
 .../io/graphson/PdtGraphSONSerializersV4Test.java  |  30 +--
 .../io/pdt/ProviderDefinedTypeRegistryTest.java    |  24 +--
 .../structure/io/pdt/ProviderDefinedTypeTest.java  |  46 ++---
 .../Gremlin.Net/Process/Traversal/GremlinLang.cs   |  11 +-
 .../GraphBinary4/Types/CompositePDTSerializer.cs   |  10 +-
 .../Structure/IProviderDefinedTypeAdapter.cs       |   8 +-
 .../Structure/ProviderDefinedAttribute.cs          |   2 +-
 .../Gremlin.Net/Structure/ProviderDefinedType.cs   |  20 +-
 .../Structure/ProviderDefinedTypeRegistry.cs       |  18 +-
 .../Driver/DriverRemoteConnectionTests.cs          |  12 +-
 .../Driver/GremlinClientTests.cs                   |  26 +--
 .../Process/Traversal/GremlinLangTests.cs          |  35 ++++
 .../IO/GraphBinary4/ProviderDefinedTypeTests.cs    |   6 +-
 .../Structure/ProviderDefinedTypeRegistryTests.cs  |  30 +--
 .../driver/remote/DriverRemoteConnection.java      |   5 +-
 gremlin-go/driver/client_test.go                   |  24 +--
 gremlin-go/driver/graphBinaryDeserializer.go       |   4 +-
 gremlin-go/driver/graphBinarySerializer_test.go    |  28 +--
 gremlin-go/driver/gremlinlang.go                   |  12 +-
 gremlin-go/driver/gremlinlang_test.go              |  41 +++-
 gremlin-go/driver/pdtRegistry.go                   |  22 +-
 gremlin-go/driver/pdtRegistry_test.go              |  12 +-
 gremlin-go/driver/providerDefinedType.go           |  14 +-
 gremlin-go/driver/providerDefinedType_test.go      |   4 +-
 gremlin-go/driver/traversal_test.go                |   6 +-
 .../python/gremlin_python/process/traversal.py     |   6 +-
 .../main/python/gremlin_python/structure/graph.py  |  24 ++-
 .../gremlin_python/structure/io/graphbinaryV4.py   |   8 +-
 .../python/tests/unit/process/test_gremlin_lang.py |  22 ++
 .../tests/unit/structure/io/test_graphbinaryV4.py  |  12 +-
 .../structure/io/test_provider_defined_type.py     |  10 +-
 gremlin-server/pom.xml                             |  10 -
 .../gremlin/server/GremlinDriverIntegrateTest.java |  12 +-
 .../GremlinServerSerializationIntegrateTest.java   |  22 +-
 .../util/ser/GraphBinaryMessageSerializerV4.java   |  13 +-
 .../binary/GraphBinaryPdtSpiAutoWiringTest.java    | 134 ++++++++++++
 .../util/ser}/binary/GraphBinaryWriterPdtTest.java |  18 +-
 .../gremlin/util/ser/binary/TestPointAdapter.java  |  63 ++++++
 .../types/ProviderDefinedTypeSerializerTest.java   |  23 ++-
 ...lin.structure.io.pdt.ProviderDefinedTypeAdapter |   1 +
 53 files changed, 713 insertions(+), 627 deletions(-)
 delete mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryIo.java
 delete mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/HeapBuffer.java
 create mode 100644 gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryPdtSpiAutoWiringTest.java
 rename {gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io => gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser}/binary/GraphBinaryWriterPdtTest.java (77%)
 create mode 100644 gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TestPointAdapter.java
 rename {gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io => gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser}/binary/types/ProviderDefinedTypeSerializerTest.java (89%)
 create mode 100644 gremlin-util/src/test/resources/META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter


(tinkerpop) 01/02: Address PDT PR review: validation, docs, precedence tests, cleanup

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a commit to branch PDT
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit c239ee6a2f6d3d85fd71323cacb481d450bd9b2e
Author: Cole Greer <co...@colegreer.ca>
AuthorDate: Wed Jun 10 20:05:15 2026 -0700

    Address PDT PR review: validation, docs, precedence tests, cleanup
    
    - Python: enforce String-only keys on ProviderDefinedType property maps to
      match the other GLVs, with a unit test.
    - Docs: document the non-empty type name and String property-key constraints
      inline in the provider PDT section (covering all GLVs); add a JPMS note
      explaining annotation-based hydration needs the provider module to open its
      package to gremlin-core; integrate the CustomTypeSerializer breaking-change
      migration guidance into the existing 4.x upgrade entry.
    - All GLVs: add a maintenance comment and a lightweight test verifying that a
      registered adapter takes precedence over the @ProviderDefined annotation
      during dehydration (Java, Python, Go, .NET).
    - Remove the now-orphaned GraphBinaryIo class (all references were removed
      earlier in this PR).
    - Remove the unused maven-jar-plugin test-jar execution from gremlin-server.
    
    Assisted-by: Kiro:claude-opus-4.8
---
 docs/src/dev/provider/index.asciidoc               | 38 ++++++++++++--
 docs/src/upgrade/release-4.x.x.asciidoc            | 15 +++---
 .../gremlin/process/traversal/GremlinLang.java     |  2 +
 .../gremlin/structure/io/binary/GraphBinaryIo.java | 60 ----------------------
 .../gremlin/process/traversal/GremlinLangTest.java | 42 +++++++++++++++
 .../Gremlin.Net/Process/Traversal/GremlinLang.cs   |  3 +-
 .../Process/Traversal/GremlinLangTests.cs          | 35 +++++++++++++
 gremlin-go/driver/gremlinlang.go                   |  4 +-
 gremlin-go/driver/gremlinlang_test.go              | 27 ++++++++++
 .../python/gremlin_python/process/traversal.py     |  4 +-
 .../main/python/gremlin_python/structure/graph.py  |  2 +
 .../python/tests/unit/process/test_gremlin_lang.py | 22 ++++++++
 .../structure/io/test_provider_defined_type.py     |  4 ++
 gremlin-server/pom.xml                             | 10 ----
 14 files changed, 185 insertions(+), 83 deletions(-)

diff --git a/docs/src/dev/provider/index.asciidoc b/docs/src/dev/provider/index.asciidoc
index 7de043bc9d..511e6a67cd 100644
--- a/docs/src/dev/provider/index.asciidoc
+++ b/docs/src/dev/provider/index.asciidoc
@@ -1362,10 +1362,11 @@ public class Point {
 }
 ----
 
-The `name` attribute is a unique identifier for the type. It is strongly recommended to namespace type names using
-your graph's identifier as a prefix (e.g. `"mygraph:Point"`). This avoids collisions when clients interact with
-multiple providers and makes the origin of a type immediately clear. By default, all fields are included. Use
-`includedFields` or `excludedFields` to control which fields are serialized:
+The `name` attribute is a unique identifier for the type. It must not be null or empty — all GLVs reject an
+empty name when a type is defined or a `ProviderDefinedType` is constructed. It is strongly recommended to namespace
+type names using your graph's identifier as a prefix (e.g. `"mygraph:Point"`). This avoids collisions when clients
+interact with multiple providers and makes the origin of a type immediately clear. By default, all fields are included.
+Use `includedFields` or `excludedFields` to control which fields are serialized:
 
 [source,java]
 ----
@@ -1382,6 +1383,10 @@ constructor and the mapped fields must be directly settable (e.g. public fields)
 requirements — for example those with immutable `final` fields or no default constructor — should instead use a
 `ProviderDefinedTypeAdapter` (see <<adapter-for-types-you-don-t-own>>), which gives full control over construction.
 
+The serialized field set becomes a property map whose keys are always strings. In statically typed GLVs (Java, .NET,
+Go, and TypeScript) this is enforced by the type system; in Python the keys are validated at runtime and a
+`TypeError` is raised for any non-string key.
+
 ==== Nested Types
 
 PDT supports nested custom types. Each nested type must also be annotated:
@@ -1541,6 +1546,31 @@ serialization and the registry handles inbound reconstruction.
 For driver users consuming PDTs, see the <<gremlin-variants,Gremlin Variants>> reference documentation for
 each language driver.
 
+==== JPMS Considerations
+
+Annotation-based PDT hydration uses reflection internally — specifically `Constructor.setAccessible(true)` and
+`Field.set()` on the provider's annotated class (see `AnnotatedTypeAdapter.fromProperties` in
+`ProviderDefinedTypeRegistry`). Under the Java Platform Module System (JPMS), this means the provider's module must
+`opens` its PDT package to `org.apache.tinkerpop.gremlin.core` (or to `ALL-UNNAMED` for classpath usage), otherwise
+the JVM will throw `InaccessibleObjectException` at hydration time.
+
+For example, in the provider's `module-info.java`:
+
+[source,java]
+----
+opens com.example.graph.types to org.apache.tinkerpop.gremlin.core;
+----
+
+Alternatively, providers that cannot modify their module descriptor can pass JVM flags:
+
+[source,text]
+----
+--add-opens=com.example.graph/com.example.graph.types=ALL-UNNAMED
+----
+
+NOTE: This only applies to annotation-based hydration. Providers using `ProviderDefinedTypeAdapter` with explicit
+`fromProperties` logic do not require reflective access and are unaffected by JPMS restrictions.
+
 [[gremlin-plugins]]
 == Gremlin Plugins
 
diff --git a/docs/src/upgrade/release-4.x.x.asciidoc b/docs/src/upgrade/release-4.x.x.asciidoc
index e0cd2e72a6..1de2d34f78 100644
--- a/docs/src/upgrade/release-4.x.x.asciidoc
+++ b/docs/src/upgrade/release-4.x.x.asciidoc
@@ -510,12 +510,15 @@ effort to the old custom serializer approach but is entirely optional for basic
 
 ===== Provider Defined Types
 
-TinkerPop 4 replaces the TP3 `CustomTypeSerializer` mechanism with Provider Defined Types (PDT). The key improvement
-is that driver users receive PDT values as structured `ProviderDefinedType` objects by default, without any
-configuration — eliminating the serializer errors that unknown custom types caused in TP3. Providers expose types by
-annotating classes with `@ProviderDefined` or implementing `ProviderDefinedTypeAdapter<T>` for types they don't own;
-adapters are discovered automatically via ServiceLoader, requiring similar effort to the old approach but benefiting
-all driver users transparently.
+TinkerPop 4 replaces the TP3 `CustomTypeSerializer` mechanism with Provider Defined Types (PDT). The TP3 extension
+point has been removed entirely and there is no backward-compatible bridge, so any existing `CustomTypeSerializer`
+implementations must be migrated: providers expose their custom types by annotating classes with `@ProviderDefined`
+or, for types they don't own, by implementing `ProviderDefinedTypeAdapter<T>`. Adapters are discovered automatically
+via ServiceLoader, so this requires similar effort to the old approach.
+
+The key improvement is that driver users now receive PDT values as structured `ProviderDefinedType` objects by
+default, without any configuration — eliminating the serializer errors that unknown custom types caused in TP3, and
+benefiting all driver users transparently.
 
 See <<provider-defined-types>> for full details on annotation usage, field filtering, nested types, and ServiceLoader
 registration.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
index 14d153e7ee..3e651be971 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
@@ -267,6 +267,8 @@ public class GremlinLang implements Cloneable, Serializable {
             return ((Class) arg).getSimpleName();
         }
 
+        // Intentional precedence: a registered adapter takes priority over @ProviderDefined annotation
+        // so that providers/users can override annotation-derived behavior with an explicit adapter.
         if (pdtRegistry != null) {
             final Optional<ProviderDefinedTypeAdapter<?>> adapter = pdtRegistry.getAdapterByClass(arg.getClass());
             if (adapter.isPresent()) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryIo.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryIo.java
deleted file mode 100644
index 8a1e2c0e4e..0000000000
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryIo.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.tinkerpop.gremlin.structure.io.binary;
-
-import org.apache.tinkerpop.gremlin.structure.io.GraphReader;
-import org.apache.tinkerpop.gremlin.structure.io.GraphWriter;
-import org.apache.tinkerpop.gremlin.structure.io.Io;
-import org.apache.tinkerpop.gremlin.structure.io.IoRegistry;
-import org.apache.tinkerpop.gremlin.structure.io.Mapper;
-
-import java.io.IOException;
-
-/**
- * This is a dummy implementation of {@link Io} which is only used in the context of helping to configure a
- * GraphBinary {@code MessageSerializer} with an {@link IoRegistry}. It's methods are not implemented.
- *
- * @author Stephen Mallette (http://stephen.genoprime.com)
- */
-public class GraphBinaryIo implements Io {
-    @Override
-    public GraphReader.ReaderBuilder reader() {
-        throw new UnsupportedOperationException("GraphBinaryIo is only used to support IoRegistry configuration - it's methods are not implemented");
-    }
-
-    @Override
-    public GraphWriter.WriterBuilder writer() {
-        throw new UnsupportedOperationException("GraphBinaryIo is only used to support IoRegistry configuration - it's methods are not implemented");
-    }
-
-    @Override
-    public Mapper.Builder mapper() {
-        throw new UnsupportedOperationException("GraphBinaryIo is only used to support IoRegistry configuration - it's methods are not implemented");
-    }
-
-    @Override
-    public void writeGraph(final String file) throws IOException {
-        throw new UnsupportedOperationException("GraphBinaryIo is only used to support IoRegistry configuration - it's methods are not implemented");
-    }
-
-    @Override
-    public void readGraph(final String file) throws IOException {
-        throw new UnsupportedOperationException("GraphBinaryIo is only used to support IoRegistry configuration - it's methods are not implemented");
-    }
-}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
index e181393de7..24988ed084 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
@@ -28,7 +28,10 @@ import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefined;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdge;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
 import org.apache.tinkerpop.gremlin.util.DatetimeHelper;
@@ -450,6 +453,45 @@ public class GremlinLangTest {
         }
     }
 
+    public static class AdapterPrecedenceTests {
+
+        /**
+         * A type annotated with @ProviderDefined that also has an explicit adapter registered.
+         * The adapter should take precedence over the annotation.
+         */
+        @ProviderDefined(name = "AnnotationName")
+        private static class DualType {
+            public int value = 42;
+
+            private DualType() {}
+            DualType(final int value) { this.value = value; }
+        }
+
+        @Test
+        public void shouldUseAdapterOverAnnotation() {
+            final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty();
+            registry.register(new ProviderDefinedTypeAdapter<DualType>() {
+                @Override public String typeName() { return "AdapterName"; }
+                @Override public Class<DualType> targetClass() { return DualType.class; }
+                @Override public Map<String, Object> toProperties(final DualType obj) {
+                    return Collections.singletonMap("v", obj.value);
+                }
+                @Override public DualType fromProperties(final Map<String, Object> properties) {
+                    return new DualType((int) properties.get("v"));
+                }
+            });
+
+            final GraphTraversalSource g2 = traversal().with(EmptyGraph.instance());
+            g2.getGremlinLang().setPdtRegistry(registry);
+            final String gremlin = g2.inject(new DualType(7)).asAdmin().getGremlinLang().getGremlin();
+
+            // adapter produces "AdapterName" with key "v", not annotation's "AnnotationName" with key "value"
+            assertTrue(gremlin, gremlin.contains("PDT(\"AdapterName\""));
+            assertTrue(gremlin, gremlin.contains("\"v\":7"));
+            assertFalse(gremlin, gremlin.contains("AnnotationName"));
+        }
+    }
+
     public static class UnsupportedTypeTests {
 
         /**
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
index fff3dcff88..bb48d96bc6 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
@@ -375,7 +375,8 @@ namespace Gremlin.Net.Process.Traversal
             if (arg is Type type)
                 return type.Name;
 
-            // Registry-based dehydration
+            // Precedence: a registered adapter intentionally takes priority over the [ProviderDefined]
+            // attribute so that explicit adapters can override attribute-derived dehydration behavior.
             if (PdtRegistry != null)
             {
                 var adapterInfo = PdtRegistry.GetAdapterByType(arg.GetType());
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
index df80b589a6..a0c15d1ef5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
@@ -1174,5 +1174,40 @@ namespace Gremlin.Net.UnitTest.Process.Traversal
             public int X { get; set; }
             public int Y { get; set; }
         }
+
+        [Fact]
+        public void g_Inject_PDT_adapter_takes_precedence_over_attribute()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.Register(new AdapterForAnnotatedPoint());
+
+            var g = new GraphTraversalSource();
+            g.GremlinLang.PdtRegistry = registry;
+
+            var point = new AnnotatedPointWithAdapter { X = 5, Y = 10 };
+            var result = g.Inject((object)point).GremlinLang.GetGremlin();
+
+            // The adapter uses type name "adapter.Point" and only exposes "a"/"b" properties,
+            // overriding the attribute which would produce "attr.Point" with "X"/"Y".
+            Assert.Equal("g.inject(PDT(\"adapter.Point\",[\"a\":5,\"b\":10]))", result);
+        }
+
+        [ProviderDefined(Name = "attr.Point")]
+        private class AnnotatedPointWithAdapter
+        {
+            public int X { get; set; }
+            public int Y { get; set; }
+        }
+
+        private class AdapterForAnnotatedPoint : IProviderDefinedTypeAdapter<AnnotatedPointWithAdapter>
+        {
+            public string TypeName => "adapter.Point";
+
+            public AnnotatedPointWithAdapter FromProperties(IReadOnlyDictionary<string, object?> properties) =>
+                new() { X = (int)properties["a"]!, Y = (int)properties["b"]! };
+
+            public IReadOnlyDictionary<string, object?> ToProperties(AnnotatedPointWithAdapter obj) =>
+                new Dictionary<string, object?> { { "a", obj.X }, { "b", obj.Y } };
+        }
     }
 }
diff --git a/gremlin-go/driver/gremlinlang.go b/gremlin-go/driver/gremlinlang.go
index 59b900eab4..dac9e64aa8 100644
--- a/gremlin-go/driver/gremlinlang.go
+++ b/gremlin-go/driver/gremlinlang.go
@@ -299,7 +299,9 @@ func (gl *GremlinLang) argAsString(arg interface{}) (string, error) {
 	case []byte:
 		return fmt.Sprintf("Binary(\"%s\")", base64.StdEncoding.EncodeToString(v)), nil
 	default:
-		// Registry-based dehydration
+		// Registry-based dehydration: a registered adapter intentionally takes precedence
+		// over any reflection/struct-based fallback, allowing explicit adapters to override
+		// default behavior for a given Go type.
 		if gl.pdtRegistry != nil {
 			adapter := gl.pdtRegistry.GetAdapterByType(reflect.TypeOf(arg))
 			if adapter != nil && adapter.ToProperties != nil {
diff --git a/gremlin-go/driver/gremlinlang_test.go b/gremlin-go/driver/gremlinlang_test.go
index c857fb5a91..9ab9b218f0 100644
--- a/gremlin-go/driver/gremlinlang_test.go
+++ b/gremlin-go/driver/gremlinlang_test.go
@@ -22,6 +22,7 @@ package gremlingo
 import (
 	"fmt"
 	"math"
+	"reflect"
 	"regexp"
 	"strings"
 	"testing"
@@ -921,3 +922,29 @@ func Test_PDT_GremlinLang(t *testing.T) {
 		}
 	})
 }
+
+// adapterPrecedencePoint is a struct type used to verify that a registered adapter
+// takes precedence over the default handling (which would panic for an unknown struct).
+type adapterPrecedencePoint struct {
+	X int32
+	Y int32
+}
+
+func Test_PDT_AdapterPrecedenceOverDefault(t *testing.T) {
+	registry := NewPDTRegistry()
+	registry.RegisterFuncsWithType("test:Point", reflect.TypeOf(adapterPrecedencePoint{}),
+		nil,
+		func(obj interface{}) (map[string]interface{}, error) {
+			p := obj.(adapterPrecedencePoint)
+			return map[string]interface{}{"x": p.X, "y": p.Y}, nil
+		})
+
+	g := NewGraphTraversalSource(nil, nil)
+	g.GetGremlinLang().pdtRegistry = registry
+
+	gremlin := g.Inject(adapterPrecedencePoint{X: 7, Y: 9}).GremlinLang.GetGremlin()
+	expected := `g.inject(PDT("test:Point",["x":7,"y":9]))`
+	if gremlin != expected {
+		t.Errorf("adapter precedence: got %v, expected %v", gremlin, expected)
+	}
+}
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index 93f835c57f..6b8bcbfea3 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -952,7 +952,9 @@ class GremlinLang(object):
         if isinstance(arg, type):
             return arg.__name__
 
-        # Registry-based dehydration
+        # Registry-based dehydration — a registered adapter intentionally takes
+        # precedence over the @provider_defined decorator fallback below, allowing
+        # explicit adapters to override decorator-derived behavior.
         if self.pdt_registry is not None:
             adapter = self.pdt_registry.get_adapter_by_class(type(arg))
             if adapter is not None and adapter['serialize'] is not None:
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/graph.py b/gremlin-python/src/main/python/gremlin_python/structure/graph.py
index 61cec674d8..6160af3c10 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/graph.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/graph.py
@@ -149,6 +149,8 @@ class ProviderDefinedType(object):
             raise ValueError("name cannot be null or empty")
         self._name = name
         self._properties = dict(properties) if properties else {}
+        if any(not isinstance(k, str) for k in self._properties):
+            raise TypeError("ProviderDefinedType property keys must be strings")
 
     @property
     def name(self):
diff --git a/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py b/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py
index 74a386163d..b73f7b8ee2 100644
--- a/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py
+++ b/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py
@@ -594,6 +594,28 @@ class TestGremlinLang(object):
         gremlin = g.inject(p).gremlin_lang.get_gremlin()
         assert "PDT('com.example.Point',['x':1,'y':2])" in gremlin
 
+    def test_pdt_adapter_takes_precedence_over_decorator(self):
+        from gremlin_python.structure.graph import ProviderDefinedTypeRegistry, provider_defined
+        g = traversal().with_(None)
+
+        @provider_defined(name="com.example.Point")
+        class Point:
+            def __init__(self, x, y):
+                self.x = x
+                self.y = y
+
+        registry = ProviderDefinedTypeRegistry()
+        registry.register("com.adapter.Point",
+                          deserialize_fn=lambda props: Point(props["a"], props["b"]),
+                          serialize_fn=lambda p: {"a": p.x, "b": p.y},
+                          target_class=Point)
+        g.gremlin_lang.pdt_registry = registry
+
+        p = Point(3, 4)
+        gremlin = g.inject(p).gremlin_lang.get_gremlin()
+        # The adapter's type_name and properties must win over the decorator's
+        assert "PDT('com.adapter.Point',['a':3,'b':4])" in gremlin
+
     def test_pdt_special_characters_in_name(self):
         from gremlin_python.structure.graph import ProviderDefinedType
         g = traversal().with_(None)
diff --git a/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py b/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
index 2d5a47cb08..32deaa9840 100644
--- a/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
+++ b/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
@@ -35,6 +35,10 @@ class TestProviderDefinedType(object):
         with pytest.raises(ValueError):
             ProviderDefinedType(None, {"x": 1})
 
+    def test_non_string_key_rejected(self):
+        with pytest.raises(TypeError):
+            ProviderDefinedType("com.example.Bad", {1: "value"})
+
 
 class TestProviderDefinedTypeRegistry(object):
 
diff --git a/gremlin-server/pom.xml b/gremlin-server/pom.xml
index 9b40be61ee..df4c5e6da6 100644
--- a/gremlin-server/pom.xml
+++ b/gremlin-server/pom.xml
@@ -173,16 +173,6 @@ limitations under the License.
                     </archive>
                 </configuration>
             </plugin>
-            <plugin>
-                <artifactId>maven-jar-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>test-jar</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>


(tinkerpop) 02/02: Address PDT PR review: SPI auto-wiring, API renames, test relocation

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a commit to branch PDT
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 2e711729bdbcc843139fdc5c3fb585f6f9dcc6dc
Author: Cole Greer <co...@colegreer.ca>
AuthorDate: Wed Jun 10 21:22:22 2026 -0700

    Address PDT PR review: SPI auto-wiring, API renames, test relocation
    
    - Auto-wire an SPI-discovered ProviderDefinedTypeRegistry by default on the
      Java driver path so registered adapters hydrate/dehydrate with zero config:
      the default GraphBinaryMessageSerializerV4 and DriverRemoteConnection now use
      ProviderDefinedTypeRegistry.create(). setPdtRegistry and explicit serializer
      registries remain as overrides.
    - Rename the registry factory build() -> create() across Java, Python, and
      .NET (Go's NewPDTRegistry and JS's constructor are already idiomatic). The
      old name misleadingly implied the Builder pattern.
    - Rename the PDT field map terminology properties -> fields across all GLVs
      (getFields/Fields, toFields/fromFields) to match the @ProviderDefined
      annotation and the GraphSON wire format, and disambiguate from Element
      properties. Wire formats and binary layouts are unchanged.
    - Document that equals()/hashCode() intentionally exclude the transient
      hydrated field.
    - Move the PDT GraphBinary serializer tests from gremlin-core to gremlin-util
      and delete the bespoke HeapBuffer, reusing the netty-backed buffer pattern.
    - Update provider and reference docs for the renames.
    
    Assisted-by: Kiro:claude-opus-4.8
---
 docs/src/dev/provider/index.asciidoc               |  18 +-
 docs/src/reference/gremlin-variants.asciidoc       |  34 +--
 .../gremlin/process/traversal/GremlinLang.java     |   4 +-
 .../types/ProviderDefinedTypeSerializer.java       |  12 +-
 .../io/graphson/PdtGraphSONSerializersV4.java      |   2 +-
 .../structure/io/pdt/ProviderDefinedType.java      |  31 +--
 .../io/pdt/ProviderDefinedTypeAdapter.java         |   6 +-
 .../io/pdt/ProviderDefinedTypeRegistry.java        |  16 +-
 .../grammar/GeneralLiteralVisitorTest.java         |   6 +-
 .../gremlin/process/traversal/GremlinLangTest.java |   4 +-
 .../gremlin/structure/io/binary/HeapBuffer.java    | 230 ---------------------
 .../io/graphson/PdtGraphSONSerializersV4Test.java  |  30 +--
 .../io/pdt/ProviderDefinedTypeRegistryTest.java    |  24 +--
 .../structure/io/pdt/ProviderDefinedTypeTest.java  |  46 ++---
 .../Gremlin.Net/Process/Traversal/GremlinLang.cs   |   8 +-
 .../GraphBinary4/Types/CompositePDTSerializer.cs   |  10 +-
 .../Structure/IProviderDefinedTypeAdapter.cs       |   8 +-
 .../Structure/ProviderDefinedAttribute.cs          |   2 +-
 .../Gremlin.Net/Structure/ProviderDefinedType.cs   |  20 +-
 .../Structure/ProviderDefinedTypeRegistry.cs       |  18 +-
 .../Driver/DriverRemoteConnectionTests.cs          |  12 +-
 .../Driver/GremlinClientTests.cs                   |  26 +--
 .../Process/Traversal/GremlinLangTests.cs          |   6 +-
 .../IO/GraphBinary4/ProviderDefinedTypeTests.cs    |   6 +-
 .../Structure/ProviderDefinedTypeRegistryTests.cs  |  30 +--
 .../driver/remote/DriverRemoteConnection.java      |   5 +-
 gremlin-go/driver/client_test.go                   |  24 +--
 gremlin-go/driver/graphBinaryDeserializer.go       |   4 +-
 gremlin-go/driver/graphBinarySerializer_test.go    |  28 +--
 gremlin-go/driver/gremlinlang.go                   |   8 +-
 gremlin-go/driver/gremlinlang_test.go              |  14 +-
 gremlin-go/driver/pdtRegistry.go                   |  22 +-
 gremlin-go/driver/pdtRegistry_test.go              |  12 +-
 gremlin-go/driver/providerDefinedType.go           |  14 +-
 gremlin-go/driver/providerDefinedType_test.go      |   4 +-
 gremlin-go/driver/traversal_test.go                |   6 +-
 .../python/gremlin_python/process/traversal.py     |   2 +-
 .../main/python/gremlin_python/structure/graph.py  |  26 +--
 .../gremlin_python/structure/io/graphbinaryV4.py   |   8 +-
 .../tests/unit/structure/io/test_graphbinaryV4.py  |  12 +-
 .../structure/io/test_provider_defined_type.py     |   6 +-
 .../gremlin/server/GremlinDriverIntegrateTest.java |  12 +-
 .../GremlinServerSerializationIntegrateTest.java   |  22 +-
 .../util/ser/GraphBinaryMessageSerializerV4.java   |  13 +-
 .../binary/GraphBinaryPdtSpiAutoWiringTest.java    | 134 ++++++++++++
 .../util/ser}/binary/GraphBinaryWriterPdtTest.java |  18 +-
 .../gremlin/util/ser/binary/TestPointAdapter.java  |  63 ++++++
 .../types/ProviderDefinedTypeSerializerTest.java   |  23 ++-
 ...lin.structure.io.pdt.ProviderDefinedTypeAdapter |   1 +
 49 files changed, 537 insertions(+), 553 deletions(-)

diff --git a/docs/src/dev/provider/index.asciidoc b/docs/src/dev/provider/index.asciidoc
index 511e6a67cd..59c1b32ca1 100644
--- a/docs/src/dev/provider/index.asciidoc
+++ b/docs/src/dev/provider/index.asciidoc
@@ -1426,13 +1426,13 @@ public class ColorAdapter implements ProviderDefinedTypeAdapter<java.awt.Color>
     public Class<java.awt.Color> targetClass() { return java.awt.Color.class; }
 
     @Override
-    public Map<String, Object> toProperties(java.awt.Color color) {
+    public Map<String, Object> toFields(java.awt.Color color) {
         return Map.of("r", color.getRed(), "g", color.getGreen(),
                       "b", color.getBlue(), "a", color.getAlpha());
     }
 
     @Override
-    public java.awt.Color fromProperties(Map<String, Object> fields) {
+    public java.awt.Color fromFields(Map<String, Object> fields) {
         return new java.awt.Color((int) fields.get("r"), (int) fields.get("g"),
                                   (int) fields.get("b"), (int) fields.get("a"));
     }
@@ -1461,12 +1461,12 @@ annotation to derive the type name and field mapping automatically:
 
 [source,java]
 ----
-ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.build();
+ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.create();
 registry.register(Point.class, Address.class, Person.class);
 ----
 
 Adapter types (for classes you don't own) are discovered automatically via `ServiceLoader` when using
-`ProviderDefinedTypeRegistry.build()`. Register them by adding a file at:
+`ProviderDefinedTypeRegistry.create()`. Register them by adding a file at:
 
 ----
 META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter
@@ -1485,9 +1485,9 @@ definition time (import time), so any annotated type round-trips without any add
 
 ===== .NET
 
-`[ProviderDefined]`-annotated types are discovered automatically. Calling `ProviderDefinedTypeRegistry.Build()`
+`[ProviderDefined]`-annotated types are discovered automatically. Calling `ProviderDefinedTypeRegistry.Create()`
 scans all loaded assemblies for `[ProviderDefined]`-annotated types and registers them for hydration. No extra
-configuration is needed — providers simply annotate their types and users call `Build()` to create the registry.
+configuration is needed — providers simply annotate their types and users call `Create()` to create the registry.
 
 ===== JavaScript
 
@@ -1524,7 +1524,7 @@ the registry is populated:
 // In the provider's client library
 public class MyGraphTypeRegistry {
     public static ProviderDefinedTypeRegistry build() {
-        ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.build(); // discovers ServiceLoader adapters
+        ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.create(); // discovers ServiceLoader adapters
         registry.register(Point.class, Address.class, Person.class);               // registers annotated types
         return registry;
     }
@@ -1549,7 +1549,7 @@ each language driver.
 ==== JPMS Considerations
 
 Annotation-based PDT hydration uses reflection internally — specifically `Constructor.setAccessible(true)` and
-`Field.set()` on the provider's annotated class (see `AnnotatedTypeAdapter.fromProperties` in
+`Field.set()` on the provider's annotated class (see `AnnotatedTypeAdapter.fromFields` in
 `ProviderDefinedTypeRegistry`). Under the Java Platform Module System (JPMS), this means the provider's module must
 `opens` its PDT package to `org.apache.tinkerpop.gremlin.core` (or to `ALL-UNNAMED` for classpath usage), otherwise
 the JVM will throw `InaccessibleObjectException` at hydration time.
@@ -1569,7 +1569,7 @@ Alternatively, providers that cannot modify their module descriptor can pass JVM
 ----
 
 NOTE: This only applies to annotation-based hydration. Providers using `ProviderDefinedTypeAdapter` with explicit
-`fromProperties` logic do not require reflective access and are unaffected by JPMS restrictions.
+`fromFields` logic do not require reflective access and are unaffected by JPMS restrictions.
 
 [[gremlin-plugins]]
 == Gremlin Plugins
diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc
index 2583e93ebf..38c252a5b2 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -627,8 +627,8 @@ Consult your graph provider's documentation for the list of PDTs they support.
 ----
 results, err := g.V().Has("location").Values("location").ToList()
 pdt := results[0].GetInterface().(*gremlingo.ProviderDefinedType)
-fmt.Println(pdt.Name)       // "x:Point"
-fmt.Println(pdt.Properties) // map[x:1.0 y:2.0]
+fmt.Println(pdt.Name)   // "x:Point"
+fmt.Println(pdt.Fields) // map[x:1.0 y:2.0]
 ----
 
 Working with raw `*ProviderDefinedType` values is always available. Using a `PDTRegistry` is an optional
@@ -1560,7 +1560,7 @@ Receiving a raw PDT:
 ----
 ProviderDefinedType pdt = (ProviderDefinedType) g.V().has("location").values("location").next();
 String typeName = pdt.getName();                   // "x:Point"
-Map<String, Object> props = pdt.getProperties();   // {x: 1.0, y: 2.0}
+Map<String, Object> fields = pdt.getFields();      // {x: 1.0, y: 2.0}
 ----
 
 Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
@@ -1571,8 +1571,8 @@ Using a `ProviderDefinedTypeRegistry` for hydration and dehydration:
 public class PointAdapter implements ProviderDefinedTypeAdapter<Point> {
     @Override public String typeName() { return "x:Point"; }
     @Override public Class<Point> targetClass() { return Point.class; }
-    @Override public Map<String, Object> toProperties(Point p) { return Map.of("x", p.getX(), "y", p.getY()); }
-    @Override public Point fromProperties(Map<String, Object> m) { return new Point((double) m.get("x"), (double) m.get("y")); }
+    @Override public Map<String, Object> toFields(Point p) { return Map.of("x", p.getX(), "y", p.getY()); }
+    @Override public Point fromFields(Map<String, Object> m) { return new Point((double) m.get("x"), (double) m.get("y")); }
 }
 ----
 
@@ -2224,8 +2224,8 @@ Receiving a raw PDT:
 ----
 const results = await g.V().has('location').values('location').toList();
 const pdt = results[0];
-console.log(pdt.name);       // "x:Point"
-console.log(pdt.properties); // { x: 1.0, y: 2.0 }
+console.log(pdt.name);   // "x:Point"
+console.log(pdt.fields); // { x: 1.0, y: 2.0 }
 ----
 
 Working with raw `ProviderDefinedType` objects is always available. Using a `ProviderDefinedTypeRegistry` is an
@@ -2707,8 +2707,8 @@ Receiving a raw PDT:
 [source,csharp]
 ----
 var pdt = (ProviderDefinedType) g.V().Has("location").Values<object>("location").Next();
-Console.WriteLine(pdt.Name);       // "x:Point"
-Console.WriteLine(pdt.Properties); // { x: 1.0, y: 2.0 }
+Console.WriteLine(pdt.Name);   // "x:Point"
+Console.WriteLine(pdt.Fields); // { x: 1.0, y: 2.0 }
 ----
 
 Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
@@ -2722,10 +2722,10 @@ public class PointAdapter : IProviderDefinedTypeAdapter<Point>
 {
     public string TypeName => "x:Point";
 
-    public Point FromProperties(IReadOnlyDictionary<string, object?> properties) =>
-        new Point((double)properties["x"], (double)properties["y"]);
+    public Point FromFields(IReadOnlyDictionary<string, object?> fields) =>
+        new Point((double)fields["x"], (double)fields["y"]);
 
-    public IReadOnlyDictionary<string, object?> ToProperties(Point value) =>
+    public IReadOnlyDictionary<string, object?> ToFields(Point value) =>
         new Dictionary<string, object?> { ["x"] = value.X, ["y"] = value.Y };
 }
 
@@ -2735,7 +2735,7 @@ registry.Register(new PointAdapter());
 using var client = new GremlinClient(new GremlinServer("localhost", 8182), pdtRegistry: registry);
 ----
 
-The `ProviderDefinedTypeRegistry.Build()` method scans loaded assemblies for `IProviderDefinedTypeAdapter<T>`
+The `ProviderDefinedTypeRegistry.Create()` method scans loaded assemblies for `IProviderDefinedTypeAdapter<T>`
 implementations and registers them automatically.
 
 For simpler cases where you own the type, annotate it directly to avoid writing an adapter:
@@ -3345,8 +3345,8 @@ Receiving a raw PDT:
 [source,python]
 ----
 pdt = g.V().has('location').values('location').next()
-print(pdt.name)       # "x:Point"
-print(pdt.properties) # {'x': 1.0, 'y': 2.0}
+print(pdt.name)   # "x:Point"
+print(pdt.fields) # {'x': 1.0, 'y': 2.0}
 ----
 
 Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
@@ -3360,7 +3360,7 @@ from gremlin_python.structure.graph import ProviderDefinedTypeRegistry
 
 registry = ProviderDefinedTypeRegistry()
 registry.register('x:Point',
-    deserialize_fn=lambda props: Point(props['x'], props['y']),
+    deserialize_fn=lambda fields: Point(fields['x'], fields['y']),
     serialize_fn=lambda p: {'x': p.x, 'y': p.y},
     target_class=Point)
 
@@ -3368,7 +3368,7 @@ g = traversal().with_(DriverRemoteConnection('http://localhost:8182/gremlin', 'g
     pdt_registry=registry))
 ----
 
-The `ProviderDefinedTypeRegistry.build()` class method discovers adapters via `entry_points` in `pyproject.toml`
+The `ProviderDefinedTypeRegistry.create()` class method discovers adapters via `entry_points` in `pyproject.toml`
 under the `tinkerpop.pdt` group.
 
 For simpler cases where you own the type, the `@provider_defined` decorator enables automatic round-trip conversion
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
index 3e651be971..1edc6217d7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
@@ -191,7 +191,7 @@ public class GremlinLang implements Cloneable, Serializable {
 
         if (arg instanceof ProviderDefinedType) {
             final ProviderDefinedType pdt = (ProviderDefinedType) arg;
-            return "PDT(" + argAsString(pdt.getName()) + "," + asString((Map<?, ?>) pdt.getProperties()) + ")";
+            return "PDT(" + argAsString(pdt.getName()) + "," + asString((Map<?, ?>) pdt.getFields()) + ")";
         }
 
         if (arg instanceof Enum) {
@@ -273,7 +273,7 @@ public class GremlinLang implements Cloneable, Serializable {
             final Optional<ProviderDefinedTypeAdapter<?>> adapter = pdtRegistry.getAdapterByClass(arg.getClass());
             if (adapter.isPresent()) {
                 @SuppressWarnings("unchecked")
-                final Map<String, Object> props = ((ProviderDefinedTypeAdapter) adapter.get()).toProperties(arg);
+                final Map<String, Object> props = ((ProviderDefinedTypeAdapter) adapter.get()).toFields(arg);
                 return argAsString(new ProviderDefinedType(adapter.get().typeName(), props));
             }
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializer.java
index d45b4cf2a9..a833d2d46a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializer.java
@@ -38,19 +38,19 @@ public class ProviderDefinedTypeSerializer extends SimpleTypeSerializer<Provider
         final String name = context.read(buffer);
         if (name == null || name.isEmpty())
             throw new IOException("ProviderDefinedType name cannot be null or empty");
-        final Map<?, ?> properties = context.read(buffer);
-        for (final Object key : properties.keySet()) {
+        final Map<?, ?> fields = context.read(buffer);
+        for (final Object key : fields.keySet()) {
             if (!(key instanceof String))
-                throw new IOException("ProviderDefinedType properties map must have String keys, found: " + key.getClass().getName());
+                throw new IOException("ProviderDefinedType fields map must have String keys, found: " + key.getClass().getName());
         }
         @SuppressWarnings("unchecked")
-        final Map<String, Object> typedProperties = (Map<String, Object>) (Map<?, ?>) properties;
-        return new ProviderDefinedType(name, typedProperties);
+        final Map<String, Object> typedFields = (Map<String, Object>) (Map<?, ?>) fields;
+        return new ProviderDefinedType(name, typedFields);
     }
 
     @Override
     protected void writeValue(final ProviderDefinedType value, final Buffer buffer, final GraphBinaryWriter context) throws IOException {
         context.write(value.getName(), buffer);
-        context.write(value.getProperties(), buffer);
+        context.write(value.getFields(), buffer);
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
index fb4cd848a3..de116f54aa 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
@@ -53,7 +53,7 @@ final class PdtGraphSONSerializersV4 {
             jsonGenerator.writeStringField("type", pdt.getName());
             jsonGenerator.writeFieldName("fields");
             jsonGenerator.writeStartObject();
-            for (final Map.Entry<String, Object> entry : pdt.getProperties().entrySet()) {
+            for (final Map.Entry<String, Object> entry : pdt.getFields().entrySet()) {
                 jsonGenerator.writeFieldName(entry.getKey());
                 jsonGenerator.writeObject(entry.getValue());
             }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedType.java
index 3a67209db9..391d06fd94 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedType.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedType.java
@@ -31,23 +31,23 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * An immutable representation of a provider-defined type consisting of a name and a map of properties.
+ * An immutable representation of a provider-defined type consisting of a name and a map of fields.
  */
 public final class ProviderDefinedType {
 
     private static final ConcurrentHashMap<Class<?>, FieldCache> FIELD_CACHE = new ConcurrentHashMap<>();
 
     private final String name;
-    private final Map<String, Object> properties;
+    private final Map<String, Object> fields;
     private Object hydrated;
 
-    public ProviderDefinedType(final String name, final Map<String, Object> properties) {
+    public ProviderDefinedType(final String name, final Map<String, Object> fields) {
         if (name == null || name.isEmpty())
             throw new IllegalArgumentException("name cannot be null or empty");
-        if (properties == null)
-            throw new IllegalArgumentException("properties cannot be null");
+        if (fields == null)
+            throw new IllegalArgumentException("fields cannot be null");
         this.name = name;
-        this.properties = Collections.unmodifiableMap(new LinkedHashMap<>(properties));
+        this.fields = Collections.unmodifiableMap(new LinkedHashMap<>(fields));
     }
 
     /**
@@ -139,15 +139,15 @@ public final class ProviderDefinedType {
         return name;
     }
 
-    public Map<String, Object> getProperties() {
-        return properties;
+    public Map<String, Object> getFields() {
+        return fields;
     }
 
     /**
      * Returns a copy of this PDT with the hydrated object attached.
      */
     public ProviderDefinedType withHydrated(final Object hydrated) {
-        final ProviderDefinedType copy = new ProviderDefinedType(this.name, this.properties);
+        final ProviderDefinedType copy = new ProviderDefinedType(this.name, this.fields);
         copy.hydrated = hydrated;
         return copy;
     }
@@ -159,21 +159,28 @@ public final class ProviderDefinedType {
         return hydrated;
     }
 
+    /**
+     * Equality is based solely on {@code name} and {@code fields} (the serialized wire form).
+     * The {@code hydrated} field is intentionally excluded — it is a transient, derived view
+     * cached by the deserializer via {@link #withHydrated(Object)} and is not part of the
+     * type's logical identity.
+     */
     @Override
     public boolean equals(final Object o) {
         if (this == o) return true;
         if (!(o instanceof ProviderDefinedType)) return false;
         final ProviderDefinedType that = (ProviderDefinedType) o;
-        return name.equals(that.name) && properties.equals(that.properties);
+        return name.equals(that.name) && fields.equals(that.fields);
     }
 
+    /** See {@link #equals(Object)} for rationale on field inclusion. */
     @Override
     public int hashCode() {
-        return Objects.hash(name, properties);
+        return Objects.hash(name, fields);
     }
 
     @Override
     public String toString() {
-        return "pdt[" + name + "]" + properties;
+        return "pdt[" + name + "]" + fields;
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeAdapter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeAdapter.java
index 86880a0584..701fba0d49 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeAdapter.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeAdapter.java
@@ -21,11 +21,11 @@ package org.apache.tinkerpop.gremlin.structure.io.pdt;
 import java.util.Map;
 
 /**
- * Adapter for converting between a typed object and a {@link ProviderDefinedType} property map.
+ * Adapter for converting between a typed object and a {@link ProviderDefinedType} field map.
  */
 public interface ProviderDefinedTypeAdapter<T> {
     String typeName();
     Class<T> targetClass();
-    Map<String, Object> toProperties(T obj);
-    T fromProperties(Map<String, Object> properties);
+    Map<String, Object> toFields(T obj);
+    T fromFields(Map<String, Object> fields);
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java
index 10d63bc92f..015b2ffee0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java
@@ -49,7 +49,7 @@ public final class ProviderDefinedTypeRegistry {
      * Creates a registry populated via {@link ServiceLoader} discovery.
      */
     @SuppressWarnings("rawtypes")
-    public static ProviderDefinedTypeRegistry build() {
+    public static ProviderDefinedTypeRegistry create() {
         final ProviderDefinedTypeRegistry registry = new ProviderDefinedTypeRegistry();
         for (final ProviderDefinedTypeAdapter adapter : ServiceLoader.load(ProviderDefinedTypeAdapter.class)) {
             registry.register(adapter);
@@ -101,14 +101,14 @@ public final class ProviderDefinedTypeRegistry {
         if (adapter == null)
             return pdt;
 
-        // recursively hydrate nested PDTs in the properties map
+        // recursively hydrate nested PDTs in the fields map
         final Map<String, Object> hydrated = new LinkedHashMap<>();
-        for (final Map.Entry<String, Object> entry : pdt.getProperties().entrySet()) {
+        for (final Map.Entry<String, Object> entry : pdt.getFields().entrySet()) {
             hydrated.put(entry.getKey(), hydrateValue(entry.getValue()));
         }
 
         try {
-            return adapter.fromProperties(hydrated);
+            return adapter.fromFields(hydrated);
         } catch (final Exception e) {
             logger.warn("Failed to hydrate ProviderDefinedType '{}', returning raw PDT: {}",
                     pdt.getName(), e.getMessage());
@@ -176,18 +176,18 @@ public final class ProviderDefinedTypeRegistry {
         @Override public Class<T> targetClass() { return targetClass; }
 
         @Override
-        public Map<String, Object> toProperties(final T obj) {
-            return ProviderDefinedType.from(obj).getProperties();
+        public Map<String, Object> toFields(final T obj) {
+            return ProviderDefinedType.from(obj).getFields();
         }
 
         @Override
-        public T fromProperties(final Map<String, Object> properties) {
+        public T fromFields(final Map<String, Object> fieldMap) {
             try {
                 final java.lang.reflect.Constructor<T> ctor = targetClass.getDeclaredConstructor();
                 ctor.setAccessible(true);
                 final T obj = ctor.newInstance();
                 for (final Field field : fields) {
-                    final Object value = properties.get(field.getName());
+                    final Object value = fieldMap.get(field.getName());
                     if (value != null)
                         field.set(obj, coerce(value, field.getType()));
                 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index 953565679b..67bf4ffef0 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -1061,8 +1061,8 @@ public class GeneralLiteralVisitorTest {
             assertThat(result, instanceOf(ProviderDefinedType.class));
             final ProviderDefinedType pdt = (ProviderDefinedType) result;
             assertEquals("MyType", pdt.getName());
-            assertEquals(1, pdt.getProperties().get("x"));
-            assertEquals("hello", pdt.getProperties().get("y"));
+            assertEquals(1, pdt.getFields().get("x"));
+            assertEquals("hello", pdt.getFields().get("y"));
         }
 
         @Test
@@ -1074,7 +1074,7 @@ public class GeneralLiteralVisitorTest {
             assertThat(result, instanceOf(ProviderDefinedType.class));
             final ProviderDefinedType pdt = (ProviderDefinedType) result;
             assertEquals("Empty", pdt.getName());
-            assertTrue(pdt.getProperties().isEmpty());
+            assertTrue(pdt.getFields().isEmpty());
         }
 
         @Test
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
index 24988ed084..1164a5610d 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java
@@ -473,10 +473,10 @@ public class GremlinLangTest {
             registry.register(new ProviderDefinedTypeAdapter<DualType>() {
                 @Override public String typeName() { return "AdapterName"; }
                 @Override public Class<DualType> targetClass() { return DualType.class; }
-                @Override public Map<String, Object> toProperties(final DualType obj) {
+                @Override public Map<String, Object> toFields(final DualType obj) {
                     return Collections.singletonMap("v", obj.value);
                 }
-                @Override public DualType fromProperties(final Map<String, Object> properties) {
+                @Override public DualType fromFields(final Map<String, Object> properties) {
                     return new DualType((int) properties.get("v"));
                 }
             });
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/HeapBuffer.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/HeapBuffer.java
deleted file mode 100644
index e7f090cd1b..0000000000
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/HeapBuffer.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.tinkerpop.gremlin.structure.io.binary;
-
-import org.apache.tinkerpop.gremlin.structure.io.Buffer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-/**
- * A simple heap-based {@link Buffer} implementation for unit testing in gremlin-core.
- */
-public class HeapBuffer implements Buffer {
-    private byte[] data;
-    private int readerIndex;
-    private int writerIndex;
-    private int markedWriterIndex;
-
-    public HeapBuffer(final int initialCapacity) {
-        this.data = new byte[initialCapacity];
-    }
-
-    public static HeapBuffer allocate(final int capacity) {
-        return new HeapBuffer(capacity);
-    }
-
-    private void ensureCapacity(final int needed) {
-        if (writerIndex + needed > data.length) {
-            data = Arrays.copyOf(data, Math.max(data.length * 2, writerIndex + needed));
-        }
-    }
-
-    @Override public int readableBytes() { return writerIndex - readerIndex; }
-    @Override public int readerIndex() { return readerIndex; }
-    @Override public Buffer readerIndex(final int readerIndex) { this.readerIndex = readerIndex; return this; }
-    @Override public int writerIndex() { return writerIndex; }
-    @Override public Buffer writerIndex(final int writerIndex) { this.writerIndex = writerIndex; return this; }
-    @Override public Buffer markWriterIndex() { this.markedWriterIndex = writerIndex; return this; }
-    @Override public Buffer resetWriterIndex() { this.writerIndex = markedWriterIndex; return this; }
-    @Override public int capacity() { return data.length; }
-    @Override public boolean isDirect() { return false; }
-
-    @Override
-    public boolean readBoolean() { return readByte() != 0; }
-
-    @Override
-    public byte readByte() { return data[readerIndex++]; }
-
-    @Override
-    public short readShort() {
-        short v = (short) ((data[readerIndex] & 0xFF) << 8 | (data[readerIndex + 1] & 0xFF));
-        readerIndex += 2;
-        return v;
-    }
-
-    @Override
-    public int readInt() {
-        int v = (data[readerIndex] & 0xFF) << 24 | (data[readerIndex + 1] & 0xFF) << 16 |
-                (data[readerIndex + 2] & 0xFF) << 8 | (data[readerIndex + 3] & 0xFF);
-        readerIndex += 4;
-        return v;
-    }
-
-    @Override
-    public long readLong() {
-        long v = ((long)(data[readerIndex] & 0xFF) << 56) | ((long)(data[readerIndex+1] & 0xFF) << 48) |
-                 ((long)(data[readerIndex+2] & 0xFF) << 40) | ((long)(data[readerIndex+3] & 0xFF) << 32) |
-                 ((long)(data[readerIndex+4] & 0xFF) << 24) | ((long)(data[readerIndex+5] & 0xFF) << 16) |
-                 ((long)(data[readerIndex+6] & 0xFF) << 8)  | ((long)(data[readerIndex+7] & 0xFF));
-        readerIndex += 8;
-        return v;
-    }
-
-    @Override
-    public float readFloat() { return Float.intBitsToFloat(readInt()); }
-
-    @Override
-    public double readDouble() { return Double.longBitsToDouble(readLong()); }
-
-    @Override
-    public Buffer readBytes(final byte[] destination) {
-        System.arraycopy(data, readerIndex, destination, 0, destination.length);
-        readerIndex += destination.length;
-        return this;
-    }
-
-    @Override
-    public Buffer readBytes(final byte[] destination, final int dstIndex, final int length) {
-        System.arraycopy(data, readerIndex, destination, dstIndex, length);
-        readerIndex += length;
-        return this;
-    }
-
-    @Override
-    public Buffer readBytes(final ByteBuffer dst) {
-        int len = dst.remaining();
-        dst.put(data, readerIndex, len);
-        readerIndex += len;
-        return this;
-    }
-
-    @Override
-    public Buffer readBytes(final OutputStream out, final int length) throws IOException {
-        out.write(data, readerIndex, length);
-        readerIndex += length;
-        return this;
-    }
-
-    @Override
-    public Buffer writeBoolean(final boolean value) { return writeByte(value ? 1 : 0); }
-
-    @Override
-    public Buffer writeByte(final int value) {
-        ensureCapacity(1);
-        data[writerIndex++] = (byte) value;
-        return this;
-    }
-
-    @Override
-    public Buffer writeShort(final int value) {
-        ensureCapacity(2);
-        data[writerIndex++] = (byte) (value >>> 8);
-        data[writerIndex++] = (byte) value;
-        return this;
-    }
-
-    @Override
-    public Buffer writeInt(final int value) {
-        ensureCapacity(4);
-        data[writerIndex++] = (byte) (value >>> 24);
-        data[writerIndex++] = (byte) (value >>> 16);
-        data[writerIndex++] = (byte) (value >>> 8);
-        data[writerIndex++] = (byte) value;
-        return this;
-    }
-
-    @Override
-    public Buffer writeLong(final long value) {
-        ensureCapacity(8);
-        data[writerIndex++] = (byte) (value >>> 56);
-        data[writerIndex++] = (byte) (value >>> 48);
-        data[writerIndex++] = (byte) (value >>> 40);
-        data[writerIndex++] = (byte) (value >>> 32);
-        data[writerIndex++] = (byte) (value >>> 24);
-        data[writerIndex++] = (byte) (value >>> 16);
-        data[writerIndex++] = (byte) (value >>> 8);
-        data[writerIndex++] = (byte) value;
-        return this;
-    }
-
-    @Override
-    public Buffer writeFloat(final float value) { return writeInt(Float.floatToIntBits(value)); }
-
-    @Override
-    public Buffer writeDouble(final double value) { return writeLong(Double.doubleToLongBits(value)); }
-
-    @Override
-    public Buffer writeBytes(final byte[] src) {
-        ensureCapacity(src.length);
-        System.arraycopy(src, 0, data, writerIndex, src.length);
-        writerIndex += src.length;
-        return this;
-    }
-
-    @Override
-    public Buffer writeBytes(final ByteBuffer src) {
-        int len = src.remaining();
-        ensureCapacity(len);
-        src.get(data, writerIndex, len);
-        writerIndex += len;
-        return this;
-    }
-
-    @Override
-    public Buffer writeBytes(final byte[] src, final int srcIndex, final int length) {
-        ensureCapacity(length);
-        System.arraycopy(src, srcIndex, data, writerIndex, length);
-        writerIndex += length;
-        return this;
-    }
-
-    @Override public boolean release() { return true; }
-    @Override public Buffer retain() { return this; }
-    @Override public int referenceCount() { return 1; }
-    @Override public int nioBufferCount() { return 1; }
-
-    @Override
-    public ByteBuffer[] nioBuffers() {
-        return new ByteBuffer[] { nioBuffer() };
-    }
-
-    @Override
-    public ByteBuffer[] nioBuffers(final int index, final int length) {
-        return new ByteBuffer[] { nioBuffer(index, length) };
-    }
-
-    @Override
-    public ByteBuffer nioBuffer() {
-        return ByteBuffer.wrap(data, readerIndex, readableBytes()).slice();
-    }
-
-    @Override
-    public ByteBuffer nioBuffer(final int index, final int length) {
-        return ByteBuffer.wrap(data, index, length).slice();
-    }
-
-    @Override
-    public Buffer getBytes(final int index, final byte[] dst) {
-        System.arraycopy(data, index, dst, 0, dst.length);
-        return this;
-    }
-}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
index 67786dbcd8..1b54c39b39 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
@@ -80,9 +80,9 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
         final ProviderDefinedType pdt = mapper.readValue(json, ProviderDefinedType.class);
 
         assertEquals("Point", pdt.getName());
-        assertEquals(2, pdt.getProperties().size());
-        assertEquals(1, pdt.getProperties().get("x"));
-        assertEquals(2, pdt.getProperties().get("y"));
+        assertEquals(2, pdt.getFields().size());
+        assertEquals(1, pdt.getFields().get("x"));
+        assertEquals(2, pdt.getFields().get("y"));
     }
 
     @Test
@@ -95,7 +95,7 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
         final ProviderDefinedType result = serializeDeserialize(mapper, original, ProviderDefinedType.class);
 
         assertEquals(original.getName(), result.getName());
-        assertEquals(original.getProperties(), result.getProperties());
+        assertEquals(original.getFields(), result.getFields());
     }
 
     @Test
@@ -122,11 +122,11 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
         // round-trip nested
         final ProviderDefinedType result = serializeDeserialize(mapper, outer, ProviderDefinedType.class);
         assertEquals("NamedPoint", result.getName());
-        assertTrue(result.getProperties().get("location") instanceof ProviderDefinedType);
-        final ProviderDefinedType nestedResult = (ProviderDefinedType) result.getProperties().get("location");
+        assertTrue(result.getFields().get("location") instanceof ProviderDefinedType);
+        final ProviderDefinedType nestedResult = (ProviderDefinedType) result.getFields().get("location");
         assertEquals("Point", nestedResult.getName());
-        assertEquals(10, nestedResult.getProperties().get("x"));
-        assertEquals(20, nestedResult.getProperties().get("y"));
+        assertEquals(10, nestedResult.getFields().get("x"));
+        assertEquals(20, nestedResult.getFields().get("y"));
     }
 
     @Test
@@ -139,9 +139,9 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
         final ProviderDefinedType result = serializeDeserialize(mapper, pdt, ProviderDefinedType.class);
 
         assertEquals("NullableType", result.getName());
-        assertEquals("test", result.getProperties().get("name"));
-        assertNull(result.getProperties().get("value"));
-        assertTrue(result.getProperties().containsKey("value"));
+        assertEquals("test", result.getFields().get("name"));
+        assertNull(result.getFields().get("value"));
+        assertTrue(result.getFields().containsKey("value"));
     }
 
     // --- Hydration tests ---
@@ -155,13 +155,13 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
     static class PointAdapter implements ProviderDefinedTypeAdapter<Point> {
         @Override public String typeName() { return "Point"; }
         @Override public Class<Point> targetClass() { return Point.class; }
-        @Override public Map<String, Object> toProperties(Point obj) {
+        @Override public Map<String, Object> toFields(Point obj) {
             final Map<String, Object> m = new HashMap<>();
             m.put("x", obj.x);
             m.put("y", obj.y);
             return m;
         }
-        @Override public Point fromProperties(Map<String, Object> properties) {
+        @Override public Point fromFields(Map<String, Object> properties) {
             return new Point((int) properties.get("x"), (int) properties.get("y"));
         }
     }
@@ -202,7 +202,7 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
 
         assertNull(result.getHydrated());
         assertEquals("Point", result.getName());
-        assertEquals(1, result.getProperties().get("x"));
+        assertEquals(1, result.getFields().get("x"));
     }
 
     @Test
@@ -225,6 +225,6 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest {
 
         assertNull(result.getHydrated());
         assertEquals("Unknown", result.getName());
-        assertEquals(1, result.getProperties().get("a"));
+        assertEquals(1, result.getFields().get("a"));
     }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java
index 13f9a05b59..acc2b01174 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java
@@ -43,13 +43,13 @@ public class ProviderDefinedTypeRegistryTest {
     static class PointAdapter implements ProviderDefinedTypeAdapter<Point> {
         @Override public String typeName() { return "Point"; }
         @Override public Class<Point> targetClass() { return Point.class; }
-        @Override public Map<String, Object> toProperties(Point obj) {
+        @Override public Map<String, Object> toFields(Point obj) {
             final Map<String, Object> m = new HashMap<>();
             m.put("x", obj.x);
             m.put("y", obj.y);
             return m;
         }
-        @Override public Point fromProperties(Map<String, Object> properties) {
+        @Override public Point fromFields(Map<String, Object> properties) {
             return new Point((int) properties.get("x"), (int) properties.get("y"));
         }
     }
@@ -64,13 +64,13 @@ public class ProviderDefinedTypeRegistryTest {
     static class LineAdapter implements ProviderDefinedTypeAdapter<Line> {
         @Override public String typeName() { return "Line"; }
         @Override public Class<Line> targetClass() { return Line.class; }
-        @Override public Map<String, Object> toProperties(Line obj) {
+        @Override public Map<String, Object> toFields(Line obj) {
             final Map<String, Object> m = new HashMap<>();
             m.put("start", obj.start);
             m.put("end", obj.end);
             return m;
         }
-        @Override public Line fromProperties(Map<String, Object> properties) {
+        @Override public Line fromFields(Map<String, Object> properties) {
             return new Line((Point) properties.get("start"), (Point) properties.get("end"));
         }
     }
@@ -79,8 +79,8 @@ public class ProviderDefinedTypeRegistryTest {
     static class FailingAdapter implements ProviderDefinedTypeAdapter<Point> {
         @Override public String typeName() { return "Failing"; }
         @Override public Class<Point> targetClass() { return Point.class; }
-        @Override public Map<String, Object> toProperties(Point obj) { return new HashMap<>(); }
-        @Override public Point fromProperties(Map<String, Object> properties) {
+        @Override public Map<String, Object> toFields(Point obj) { return new HashMap<>(); }
+        @Override public Point fromFields(Map<String, Object> properties) {
             throw new RuntimeException("intentional failure");
         }
     }
@@ -162,7 +162,7 @@ public class ProviderDefinedTypeRegistryTest {
         final ProviderDefinedType linePdt = new ProviderDefinedType("Line", lineProps);
 
         // Line adapter will receive ProviderDefinedType values for start/end since Point is not registered.
-        // The LineAdapter.fromProperties casts to Point which will throw ClassCastException,
+        // The LineAdapter.fromFields casts to Point which will throw ClassCastException,
         // so hydrate should fall back to returning the raw PDT.
         final Object result = registry.hydrate(linePdt);
         assertSame(linePdt, result);
@@ -202,13 +202,13 @@ public class ProviderDefinedTypeRegistryTest {
     static class PolygonAdapter implements ProviderDefinedTypeAdapter<Polygon> {
         @Override public String typeName() { return "Polygon"; }
         @Override public Class<Polygon> targetClass() { return Polygon.class; }
-        @Override public Map<String, Object> toProperties(Polygon obj) {
+        @Override public Map<String, Object> toFields(Polygon obj) {
             final Map<String, Object> m = new HashMap<>();
             m.put("vertices", obj.vertices);
             return m;
         }
         @SuppressWarnings("unchecked")
-        @Override public Polygon fromProperties(Map<String, Object> properties) {
+        @Override public Polygon fromFields(Map<String, Object> properties) {
             return new Polygon((List<Point>) properties.get("vertices"));
         }
     }
@@ -249,9 +249,9 @@ public class ProviderDefinedTypeRegistryTest {
         registry.register(new ProviderDefinedTypeAdapter<Map>() {
             @Override public String typeName() { return "PointMap"; }
             @Override public Class<Map> targetClass() { return Map.class; }
-            @Override public Map<String, Object> toProperties(Map obj) { return new HashMap<>(); }
+            @Override public Map<String, Object> toFields(Map obj) { return new HashMap<>(); }
             @SuppressWarnings("unchecked")
-            @Override public Map fromProperties(Map<String, Object> properties) {
+            @Override public Map fromFields(Map<String, Object> properties) {
                 return (Map<String, Object>) properties.get("points");
             }
         });
@@ -283,7 +283,7 @@ public class ProviderDefinedTypeRegistryTest {
     public void shouldBuildViaServiceLoader() {
         // ServiceLoader.load will find adapters on the classpath. With no META-INF/services file
         // in test scope, this should produce an empty registry that still functions.
-        final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.build();
+        final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.create();
 
         final Map<String, Object> props = new HashMap<>();
         props.put("x", 1);
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeTest.java
index 2c153700c8..d39d152951 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeTest.java
@@ -99,7 +99,7 @@ public class ProviderDefinedTypeTest {
         props.put("y", 2);
         final ProviderDefinedType pdt = new ProviderDefinedType("Point", props);
         assertEquals("Point", pdt.getName());
-        assertEquals(props, pdt.getProperties());
+        assertEquals(props, pdt.getFields());
     }
 
     @Test
@@ -108,7 +108,7 @@ public class ProviderDefinedTypeTest {
         props.put("x", 1);
         final ProviderDefinedType pdt = new ProviderDefinedType("Point", props);
         props.put("y", 2);
-        assertEquals(1, pdt.getProperties().size());
+        assertEquals(1, pdt.getFields().size());
     }
 
     @Test
@@ -116,15 +116,15 @@ public class ProviderDefinedTypeTest {
         final Map<String, Object> props = new HashMap<>();
         props.put("x", 1);
         final ProviderDefinedType pdt = new ProviderDefinedType("Point", props);
-        assertThrows(UnsupportedOperationException.class, () -> pdt.getProperties().put("y", 2));
+        assertThrows(UnsupportedOperationException.class, () -> pdt.getFields().put("y", 2));
     }
 
     @Test
     public void shouldCreateFromAnnotatedObject() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new Point());
         assertEquals("Point", pdt.getName());
-        assertEquals(1, pdt.getProperties().get("x"));
-        assertEquals(2, pdt.getProperties().get("y"));
+        assertEquals(1, pdt.getFields().get("x"));
+        assertEquals(2, pdt.getFields().get("y"));
     }
 
     @Test
@@ -136,16 +136,16 @@ public class ProviderDefinedTypeTest {
     @Test
     public void shouldFilterWithIncludedFields() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new IncludedFieldsPoint());
-        assertEquals(1, pdt.getProperties().size());
-        assertEquals(10, pdt.getProperties().get("x"));
+        assertEquals(1, pdt.getFields().size());
+        assertEquals(10, pdt.getFields().get("x"));
     }
 
     @Test
     public void shouldFilterWithExcludedFields() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new ExcludedFieldsPoint());
-        assertEquals(2, pdt.getProperties().size());
-        assertEquals(10, pdt.getProperties().get("x"));
-        assertEquals(20, pdt.getProperties().get("y"));
+        assertEquals(2, pdt.getFields().size());
+        assertEquals(10, pdt.getFields().get("x"));
+        assertEquals(20, pdt.getFields().get("y"));
     }
 
     @Test
@@ -189,9 +189,9 @@ public class ProviderDefinedTypeTest {
     @Test
     public void shouldPreserveNullFieldValues() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new NullFieldPoint());
-        assertEquals(2, pdt.getProperties().size());
-        assertEquals(null, pdt.getProperties().get("label"));
-        assertEquals(5, pdt.getProperties().get("x"));
+        assertEquals(2, pdt.getFields().size());
+        assertEquals(null, pdt.getFields().get("label"));
+        assertEquals(5, pdt.getFields().get("x"));
     }
 
     @Test
@@ -203,25 +203,25 @@ public class ProviderDefinedTypeTest {
     public void shouldIncludeInheritedFields() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new InheritedPoint());
         assertEquals("GeoPoint", pdt.getName());
-        assertEquals(3, pdt.getProperties().size());
-        assertEquals("origin", pdt.getProperties().get("label"));
-        assertEquals(1, pdt.getProperties().get("x"));
-        assertEquals(2, pdt.getProperties().get("y"));
+        assertEquals(3, pdt.getFields().size());
+        assertEquals("origin", pdt.getFields().get("label"));
+        assertEquals(1, pdt.getFields().get("x"));
+        assertEquals(2, pdt.getFields().get("y"));
     }
 
     @Test
     public void shouldExcludeInheritedFields() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new InheritedExcluded());
-        assertEquals(2, pdt.getProperties().size());
-        assertEquals("test", pdt.getProperties().get("label"));
-        assertEquals(1, pdt.getProperties().get("x"));
+        assertEquals(2, pdt.getFields().size());
+        assertEquals("test", pdt.getFields().get("label"));
+        assertEquals(1, pdt.getFields().get("x"));
     }
 
     @Test
     public void shouldIncludeOnlySpecifiedFieldsAcrossHierarchy() {
         final ProviderDefinedType pdt = ProviderDefinedType.from(new InheritedIncluded());
-        assertEquals(2, pdt.getProperties().size());
-        assertEquals("included", pdt.getProperties().get("label"));
-        assertEquals(1, pdt.getProperties().get("x"));
+        assertEquals(2, pdt.getFields().size());
+        assertEquals("included", pdt.getFields().get("label"));
+        assertEquals(1, pdt.getFields().get("x"));
     }
 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
index bb48d96bc6..b6d1d24267 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
@@ -340,14 +340,14 @@ namespace Gremlin.Net.Process.Traversal
             if (arg is ProviderDefinedType pdt)
             {
                 var sb2 = new StringBuilder("[");
-                var count = pdt.Properties.Count;
+                var count = pdt.Fields.Count;
                 if (count == 0)
                 {
                     sb2.Append(':');
                 }
                 else
                 {
-                    foreach (var kvp in pdt.Properties)
+                    foreach (var kvp in pdt.Fields)
                     {
                         sb2.Append(ArgAsString(kvp.Key)).Append(':').Append(ArgAsString(kvp.Value));
                         if (--count > 0) sb2.Append(',');
@@ -382,8 +382,8 @@ namespace Gremlin.Net.Process.Traversal
                 var adapterInfo = PdtRegistry.GetAdapterByType(arg.GetType());
                 if (adapterInfo != null)
                 {
-                    var (adapterTypeName, toProperties) = adapterInfo.Value;
-                    var props = toProperties(arg);
+                    var (adapterTypeName, toFields) = adapterInfo.Value;
+                    var props = toFields(arg);
                     return ArgAsString(new ProviderDefinedType(adapterTypeName,
                         new Dictionary<string, object?>(props)));
                 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/CompositePDTSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/CompositePDTSerializer.cs
index 49c4bdff66..10e2a082fa 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/CompositePDTSerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/CompositePDTSerializer.cs
@@ -47,8 +47,8 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4.Types
         {
             // Write name as fully-qualified string
             await writer.WriteAsync(value.Name, stream, cancellationToken).ConfigureAwait(false);
-            // Write properties as fully-qualified map
-            await writer.WriteAsync((IDictionary<string, object?>)new Dictionary<string, object?>(value.Properties),
+            // Write fields as fully-qualified map
+            await writer.WriteAsync((IDictionary<string, object?>)new Dictionary<string, object?>(value.Fields),
                 stream, cancellationToken).ConfigureAwait(false);
         }
 
@@ -63,16 +63,16 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4.Types
             var map = await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false)
                 as IDictionary<object, object?>;
 
-            var properties = new Dictionary<string, object?>();
+            var fields = new Dictionary<string, object?>();
             if (map != null)
             {
                 foreach (var kv in map)
                 {
-                    properties[(string)kv.Key] = kv.Value;
+                    fields[(string)kv.Key] = kv.Value;
                 }
             }
 
-            return new ProviderDefinedType(name!, properties);
+            return new ProviderDefinedType(name!, fields);
         }
     }
 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IProviderDefinedTypeAdapter.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IProviderDefinedTypeAdapter.cs
index cd7af23e3c..89dce66cfc 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IProviderDefinedTypeAdapter.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IProviderDefinedTypeAdapter.cs
@@ -37,13 +37,13 @@ namespace Gremlin.Net.Structure
         string TypeName { get; }
 
         /// <summary>
-        /// Creates a typed instance from the PDT properties.
+        /// Creates a typed instance from the PDT fields.
         /// </summary>
-        T FromProperties(IReadOnlyDictionary<string, object?> properties);
+        T FromFields(IReadOnlyDictionary<string, object?> fields);
 
         /// <summary>
-        /// Converts a typed instance back to PDT properties.
+        /// Converts a typed instance back to PDT fields.
         /// </summary>
-        IReadOnlyDictionary<string, object?> ToProperties(T obj);
+        IReadOnlyDictionary<string, object?> ToFields(T obj);
     }
 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedAttribute.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedAttribute.cs
index c7d25667e5..f9a418e01a 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedAttribute.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedAttribute.cs
@@ -67,7 +67,7 @@ namespace Gremlin.Net.Structure
             if (!RegisteredTypes.TryGetValue(pdt.Name, out var type))
                 return pdt;
             var obj = Activator.CreateInstance(type)!;
-            foreach (var (key, value) in pdt.Properties)
+            foreach (var (key, value) in pdt.Fields)
             {
                 var prop = type.GetProperty(key, BindingFlags.Public | BindingFlags.Instance);
                 if (prop != null && prop.CanWrite && value != null)
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedType.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedType.cs
index ac6c9d60f9..69c005c188 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedType.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedType.cs
@@ -28,7 +28,7 @@ using System.Linq;
 namespace Gremlin.Net.Structure
 {
     /// <summary>
-    /// Represents a provider-defined type (PDT) with a name and a set of properties.
+    /// Represents a provider-defined type (PDT) with a name and a set of fields.
     /// </summary>
     public class ProviderDefinedType
     {
@@ -36,12 +36,12 @@ namespace Gremlin.Net.Structure
         /// Initializes a new instance of the <see cref="ProviderDefinedType"/> class.
         /// </summary>
         /// <param name="name">The fully-qualified name of the provider-defined type.</param>
-        /// <param name="properties">The properties of the provider-defined type.</param>
-        public ProviderDefinedType(string name, IReadOnlyDictionary<string, object?> properties)
+        /// <param name="fields">The fields of the provider-defined type.</param>
+        public ProviderDefinedType(string name, IReadOnlyDictionary<string, object?> fields)
         {
             Name = name ?? throw new ArgumentNullException(nameof(name));
             if (string.IsNullOrEmpty(name)) throw new ArgumentException("name cannot be empty", nameof(name));
-            Properties = properties ?? new Dictionary<string, object?>();
+            Fields = fields ?? new Dictionary<string, object?>();
         }
 
         /// <summary>
@@ -50,21 +50,21 @@ namespace Gremlin.Net.Structure
         public string Name { get; }
 
         /// <summary>
-        /// Gets the properties of this provider-defined type.
+        /// Gets the fields of this provider-defined type.
         /// </summary>
-        public IReadOnlyDictionary<string, object?> Properties { get; }
+        public IReadOnlyDictionary<string, object?> Fields { get; }
 
         /// <inheritdoc />
         public override string ToString() =>
-            $"pdt[{Name}]{{{string.Join(", ", Properties.Select(kv => $"{kv.Key}={kv.Value}"))}}}";
+            $"pdt[{Name}]{{{string.Join(", ", Fields.Select(kv => $"{kv.Key}={kv.Value}"))}}}";
 
         /// <inheritdoc />
         public override bool Equals(object? obj) =>
             obj is ProviderDefinedType other && Name == other.Name &&
-            Properties.Count == other.Properties.Count &&
-            Properties.All(kv => other.Properties.TryGetValue(kv.Key, out var v) && Equals(kv.Value, v));
+            Fields.Count == other.Fields.Count &&
+            Fields.All(kv => other.Fields.TryGetValue(kv.Key, out var v) && Equals(kv.Value, v));
 
         /// <inheritdoc />
-        public override int GetHashCode() => HashCode.Combine(Name, Properties.Count);
+        public override int GetHashCode() => HashCode.Combine(Name, Fields.Count);
     }
 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
index b33269c98c..530c7979bf 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
@@ -54,7 +54,7 @@ namespace Gremlin.Net.Structure
         /// <item>Types annotated with <see cref="ProviderDefinedAttribute"/> (annotation-based round-trip)</item>
         /// </list>
         /// </summary>
-        public static ProviderDefinedTypeRegistry Build()
+        public static ProviderDefinedTypeRegistry Create()
         {
             var registry = new ProviderDefinedTypeRegistry();
 
@@ -103,13 +103,13 @@ namespace Gremlin.Net.Structure
         }
 
         /// <summary>
-        /// Returns the type name and ToProperties method for the given CLR type, or null if not registered.
+        /// Returns the type name and ToFields method for the given CLR type, or null if not registered.
         /// </summary>
         internal (string typeName, Func<object, IReadOnlyDictionary<string, object?>>)? GetAdapterByType(Type type)
         {
             if (!_adaptersByType.TryGetValue(type, out var entry))
                 return null;
-            var method = entry.adapter.GetType().GetMethod("ToProperties");
+            var method = entry.adapter.GetType().GetMethod("ToFields");
             if (method == null) return null;
             return (entry.typeName, obj => (IReadOnlyDictionary<string, object?>)method.Invoke(entry.adapter, new[] { obj })!);
         }
@@ -124,15 +124,15 @@ namespace Gremlin.Net.Structure
                 return pdt;
             try
             {
-                var hydratedProps = new Dictionary<string, object?>();
-                foreach (var (key, value) in pdt.Properties)
+                var hydratedFields = new Dictionary<string, object?>();
+                foreach (var (key, value) in pdt.Fields)
                 {
-                    hydratedProps[key] = value is ProviderDefinedType nested ? Hydrate(nested) : value;
+                    hydratedFields[key] = value is ProviderDefinedType nested ? Hydrate(nested) : value;
                 }
 
-                var readOnlyProps = new ReadOnlyDictionary<string, object?>(hydratedProps);
-                var method = adapterObj.GetType().GetMethod("FromProperties");
-                return method!.Invoke(adapterObj, new object[] { readOnlyProps })!;
+                var readOnlyFields = new ReadOnlyDictionary<string, object?>(hydratedFields);
+                var method = adapterObj.GetType().GetMethod("FromFields");
+                return method!.Invoke(adapterObj, new object[] { readOnlyFields })!;
             }
             catch (Exception)
             {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
index 1f6ce57e01..3aaedb218e 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
@@ -113,8 +113,8 @@ public class DriverRemoteConnectionTests
         Assert.Single(results);
         var result = Assert.IsType<ProviderDefinedType>(results[0]);
         Assert.Equal("TestPoint", result.Name);
-        Assert.Equal(1, result.Properties["x"]);
-        Assert.Equal(2, result.Properties["y"]);
+        Assert.Equal(1, result.Fields["x"]);
+        Assert.Equal(2, result.Fields["y"]);
     }
 
     [Fact]
@@ -168,16 +168,16 @@ public class DriverRemoteConnectionTests
     {
         public string TypeName => "TestPoint";
 
-        public TestPointClass FromProperties(IReadOnlyDictionary<string, object?> properties)
+        public TestPointClass FromFields(IReadOnlyDictionary<string, object?> fields)
         {
             return new TestPointClass
             {
-                X = Convert.ToInt32(properties["x"]),
-                Y = Convert.ToInt32(properties["y"])
+                X = Convert.ToInt32(fields["x"]),
+                Y = Convert.ToInt32(fields["y"])
             };
         }
 
-        public IReadOnlyDictionary<string, object?> ToProperties(TestPointClass obj)
+        public IReadOnlyDictionary<string, object?> ToFields(TestPointClass obj)
         {
             return new ReadOnlyDictionary<string, object?>(
                 new Dictionary<string, object?> { { "x", obj.X }, { "y", obj.Y } });
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
index fab9af53e4..a5fdac7c8e 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
@@ -219,9 +219,9 @@ namespace Gremlin.Net.IntegrationTest.Driver
             Assert.Single(results);
             var pdt = Assert.IsType<ProviderDefinedType>(results[0]);
             Assert.Equal("Point", pdt.Name);
-            Assert.Equal(2, pdt.Properties.Count);
-            Assert.Equal(1, pdt.Properties["x"]);
-            Assert.Equal(2, pdt.Properties["y"]);
+            Assert.Equal(2, pdt.Fields.Count);
+            Assert.Equal(1, pdt.Fields["x"]);
+            Assert.Equal(2, pdt.Fields["y"]);
         }
 
         [Fact]
@@ -238,14 +238,14 @@ namespace Gremlin.Net.IntegrationTest.Driver
             Assert.Single(results);
             var pdt = Assert.IsType<ProviderDefinedType>(results[0]);
             Assert.Equal("Person", pdt.Name);
-            Assert.Equal("Alice", pdt.Properties["name"]);
-            Assert.Equal(30, pdt.Properties["age"]);
+            Assert.Equal("Alice", pdt.Fields["name"]);
+            Assert.Equal(30, pdt.Fields["age"]);
 
-            var address = Assert.IsType<ProviderDefinedType>(pdt.Properties["address"]);
+            var address = Assert.IsType<ProviderDefinedType>(pdt.Fields["address"]);
             Assert.Equal("Address", address.Name);
-            Assert.Equal("123 Main St", address.Properties["street"]);
-            Assert.Equal("Springfield", address.Properties["city"]);
-            Assert.Equal("12345", address.Properties["zip"]);
+            Assert.Equal("123 Main St", address.Fields["street"]);
+            Assert.Equal("Springfield", address.Fields["city"]);
+            Assert.Equal("12345", address.Fields["zip"]);
         }
 
         [Fact]
@@ -264,13 +264,13 @@ namespace Gremlin.Net.IntegrationTest.Driver
 
             var p1 = Assert.IsType<ProviderDefinedType>(list[0]);
             Assert.Equal("Point", p1.Name);
-            Assert.Equal(1, p1.Properties["x"]);
-            Assert.Equal(2, p1.Properties["y"]);
+            Assert.Equal(1, p1.Fields["x"]);
+            Assert.Equal(2, p1.Fields["y"]);
 
             var p2 = Assert.IsType<ProviderDefinedType>(list[1]);
             Assert.Equal("Point", p2.Name);
-            Assert.Equal(3, p2.Properties["x"]);
-            Assert.Equal(4, p2.Properties["y"]);
+            Assert.Equal(3, p2.Fields["x"]);
+            Assert.Equal(4, p2.Fields["y"]);
         }
     }
 }
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
index a0c15d1ef5..e6dc565d72 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
@@ -1203,10 +1203,10 @@ namespace Gremlin.Net.UnitTest.Process.Traversal
         {
             public string TypeName => "adapter.Point";
 
-            public AnnotatedPointWithAdapter FromProperties(IReadOnlyDictionary<string, object?> properties) =>
-                new() { X = (int)properties["a"]!, Y = (int)properties["b"]! };
+            public AnnotatedPointWithAdapter FromFields(IReadOnlyDictionary<string, object?> fields) =>
+                new() { X = (int)fields["a"]!, Y = (int)fields["b"]! };
 
-            public IReadOnlyDictionary<string, object?> ToProperties(AnnotatedPointWithAdapter obj) =>
+            public IReadOnlyDictionary<string, object?> ToFields(AnnotatedPointWithAdapter obj) =>
                 new Dictionary<string, object?> { { "a", obj.X }, { "b", obj.Y } };
         }
     }
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/ProviderDefinedTypeTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/ProviderDefinedTypeTests.cs
index 36a6318b52..c8b3b008f8 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/ProviderDefinedTypeTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/ProviderDefinedTypeTests.cs
@@ -49,7 +49,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4
 
             Assert.NotNull(actual);
             Assert.Equal(expected.Name, actual!.Name);
-            Assert.Equal(expected.Properties, actual.Properties);
+            Assert.Equal(expected.Fields, actual.Fields);
         }
 
         [Fact]
@@ -64,7 +64,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4
 
             Assert.NotNull(actual);
             Assert.Equal(expected.Name, actual!.Name);
-            Assert.Empty(actual.Properties);
+            Assert.Empty(actual.Fields);
         }
 
         [Fact]
@@ -80,7 +80,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4
 
             Assert.NotNull(actual);
             Assert.Equal(expected.Name, actual!.Name);
-            Assert.Null(actual.Properties["key"]);
+            Assert.Null(actual.Fields["key"]);
         }
 
         [Fact]
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/ProviderDefinedTypeRegistryTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/ProviderDefinedTypeRegistryTests.cs
index a9aefc6ee2..512b3f31d5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/ProviderDefinedTypeRegistryTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/ProviderDefinedTypeRegistryTests.cs
@@ -103,17 +103,17 @@ namespace Gremlin.Net.UnitTest.Structure
         }
 
         [Fact]
-        public void BuildShouldReturnRegistryWithoutCrashing()
+        public void CreateShouldReturnRegistryWithoutCrashing()
         {
-            var registry = ProviderDefinedTypeRegistry.Build();
+            var registry = ProviderDefinedTypeRegistry.Create();
 
             Assert.NotNull(registry);
         }
 
         [Fact]
-        public void BuildShouldDiscoverAdapterFromAssembly()
+        public void CreateShouldDiscoverAdapterFromAssembly()
         {
-            var registry = ProviderDefinedTypeRegistry.Build();
+            var registry = ProviderDefinedTypeRegistry.Create();
             var pdt = new ProviderDefinedType("test:Discoverable",
                 new Dictionary<string, object?> { ["value"] = "hello" });
 
@@ -144,10 +144,10 @@ namespace Gremlin.Net.UnitTest.Structure
         {
             public string TypeName => "geo:Point";
 
-            public Point FromProperties(IReadOnlyDictionary<string, object?> properties) =>
-                new() { X = (double)properties["x"]!, Y = (double)properties["y"]! };
+            public Point FromFields(IReadOnlyDictionary<string, object?> fields) =>
+                new() { X = (double)fields["x"]!, Y = (double)fields["y"]! };
 
-            public IReadOnlyDictionary<string, object?> ToProperties(Point obj) =>
+            public IReadOnlyDictionary<string, object?> ToFields(Point obj) =>
                 new Dictionary<string, object?> { ["x"] = obj.X, ["y"] = obj.Y };
         }
 
@@ -155,10 +155,10 @@ namespace Gremlin.Net.UnitTest.Structure
         {
             public string TypeName => "geo:Line";
 
-            public Line FromProperties(IReadOnlyDictionary<string, object?> properties) =>
-                new() { Start = (Point)properties["start"]!, End = (Point)properties["end"]! };
+            public Line FromFields(IReadOnlyDictionary<string, object?> fields) =>
+                new() { Start = (Point)fields["start"]!, End = (Point)fields["end"]! };
 
-            public IReadOnlyDictionary<string, object?> ToProperties(Line obj) =>
+            public IReadOnlyDictionary<string, object?> ToFields(Line obj) =>
                 new Dictionary<string, object?> { ["start"] = obj.Start, ["end"] = obj.End };
         }
 
@@ -166,10 +166,10 @@ namespace Gremlin.Net.UnitTest.Structure
         {
             public string TypeName => "bad:Type";
 
-            public object FromProperties(IReadOnlyDictionary<string, object?> properties) =>
+            public object FromFields(IReadOnlyDictionary<string, object?> fields) =>
                 throw new InvalidOperationException("intentional failure");
 
-            public IReadOnlyDictionary<string, object?> ToProperties(object obj) =>
+            public IReadOnlyDictionary<string, object?> ToFields(object obj) =>
                 throw new InvalidOperationException("intentional failure");
         }
 
@@ -187,10 +187,10 @@ namespace Gremlin.Net.UnitTest.Structure
     {
         public string TypeName => "test:Discoverable";
 
-        public DiscoverableType FromProperties(IReadOnlyDictionary<string, object?> properties) =>
-            new() { Value = (string)properties["value"]! };
+        public DiscoverableType FromFields(IReadOnlyDictionary<string, object?> fields) =>
+            new() { Value = (string)fields["value"]! };
 
-        public IReadOnlyDictionary<string, object?> ToProperties(DiscoverableType obj) =>
+        public IReadOnlyDictionary<string, object?> ToFields(DiscoverableType obj) =>
             new Dictionary<string, object?> { ["value"] = obj.Value };
     }
 }
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/remote/DriverRemoteConnection.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/remote/DriverRemoteConnection.java
index 3643aa9415..6c2fea1e71 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/remote/DriverRemoteConnection.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/remote/DriverRemoteConnection.java
@@ -55,7 +55,10 @@ public class DriverRemoteConnection implements RemoteConnection {
     private transient Optional<Configuration> conf = Optional.empty();
 
     private final boolean attachElements;
-    private ProviderDefinedTypeRegistry pdtRegistry;
+
+    // Default to SPI-discovered adapters so PDT dehydration works with zero configuration.
+    // This performs a lightweight ServiceLoader scan; the serializer path does its own independent scan.
+    private ProviderDefinedTypeRegistry pdtRegistry = ProviderDefinedTypeRegistry.create();
 
     public DriverRemoteConnection(final Configuration conf) {
         final boolean hasClusterConf = IteratorUtils.anyMatch(conf.getKeys(), k -> k.startsWith("clusterConfiguration"));
diff --git a/gremlin-go/driver/client_test.go b/gremlin-go/driver/client_test.go
index c234a99918..ffffcad5cb 100644
--- a/gremlin-go/driver/client_test.go
+++ b/gremlin-go/driver/client_test.go
@@ -318,8 +318,8 @@ func TestProviderDefinedTypeIntegration(t *testing.T) {
 		pdt, ok := result.Data.(*ProviderDefinedType)
 		require.True(t, ok, "expected *ProviderDefinedType, got %T", result.Data)
 		assert.Equal(t, "Point", pdt.Name)
-		assert.Equal(t, int32(1), pdt.Properties["x"])
-		assert.Equal(t, int32(2), pdt.Properties["y"])
+		assert.Equal(t, int32(1), pdt.Fields["x"])
+		assert.Equal(t, int32(2), pdt.Fields["y"])
 	})
 
 	t.Run("nested PDT (Person with Address)", func(t *testing.T) {
@@ -343,15 +343,15 @@ func TestProviderDefinedTypeIntegration(t *testing.T) {
 		pdt, ok := result.Data.(*ProviderDefinedType)
 		require.True(t, ok, "expected *ProviderDefinedType, got %T", result.Data)
 		assert.Equal(t, "Person", pdt.Name)
-		assert.Equal(t, "Alice", pdt.Properties["name"])
-		assert.Equal(t, int32(30), pdt.Properties["age"])
+		assert.Equal(t, "Alice", pdt.Fields["name"])
+		assert.Equal(t, int32(30), pdt.Fields["age"])
 
-		address, ok := pdt.Properties["address"].(*ProviderDefinedType)
+		address, ok := pdt.Fields["address"].(*ProviderDefinedType)
 		require.True(t, ok, "expected nested *ProviderDefinedType for address")
 		assert.Equal(t, "Address", address.Name)
-		assert.Equal(t, "123 Main St", address.Properties["street"])
-		assert.Equal(t, "Springfield", address.Properties["city"])
-		assert.Equal(t, "12345", address.Properties["zip"])
+		assert.Equal(t, "123 Main St", address.Fields["street"])
+		assert.Equal(t, "Springfield", address.Fields["city"])
+		assert.Equal(t, "12345", address.Fields["zip"])
 	})
 
 	t.Run("PDT in collection", func(t *testing.T) {
@@ -378,13 +378,13 @@ func TestProviderDefinedTypeIntegration(t *testing.T) {
 		p1, ok := list[0].(*ProviderDefinedType)
 		require.True(t, ok)
 		assert.Equal(t, "Point", p1.Name)
-		assert.Equal(t, int32(1), p1.Properties["x"])
-		assert.Equal(t, int32(2), p1.Properties["y"])
+		assert.Equal(t, int32(1), p1.Fields["x"])
+		assert.Equal(t, int32(2), p1.Fields["y"])
 
 		p2, ok := list[1].(*ProviderDefinedType)
 		require.True(t, ok)
 		assert.Equal(t, "Point", p2.Name)
-		assert.Equal(t, int32(3), p2.Properties["x"])
-		assert.Equal(t, int32(4), p2.Properties["y"])
+		assert.Equal(t, int32(3), p2.Fields["x"])
+		assert.Equal(t, int32(4), p2.Fields["y"])
 	})
 }
\ No newline at end of file
diff --git a/gremlin-go/driver/graphBinaryDeserializer.go b/gremlin-go/driver/graphBinaryDeserializer.go
index 8b20080cb0..dda90cfa52 100644
--- a/gremlin-go/driver/graphBinaryDeserializer.go
+++ b/gremlin-go/driver/graphBinaryDeserializer.go
@@ -639,14 +639,14 @@ func (d *GraphBinaryDeserializer) readCompositePDT() (interface{}, error) {
 	if propsObj != nil {
 		raw, ok := propsObj.(map[interface{}]interface{})
 		if !ok {
-			return nil, fmt.Errorf("ProviderDefinedType properties must be a map")
+			return nil, fmt.Errorf("ProviderDefinedType fields must be a map")
 		}
 		props = make(map[string]interface{}, len(raw))
 		for k, v := range raw {
 			props[fmt.Sprint(k)] = v
 		}
 	}
-	pdt := &ProviderDefinedType{Name: name, Properties: props}
+	pdt := &ProviderDefinedType{Name: name, Fields: props}
 	if d.pdtRegistry != nil {
 		hydrated := d.pdtRegistry.Hydrate(pdt)
 		if hydrated != pdt {
diff --git a/gremlin-go/driver/graphBinarySerializer_test.go b/gremlin-go/driver/graphBinarySerializer_test.go
index ea43a45e6d..e6f8cda81b 100644
--- a/gremlin-go/driver/graphBinarySerializer_test.go
+++ b/gremlin-go/driver/graphBinarySerializer_test.go
@@ -541,8 +541,8 @@ func TestProviderDefinedTypeSerialization(t *testing.T) {
 
 	t.Run("round-trip simple PDT", func(t *testing.T) {
 		source := &ProviderDefinedType{
-			Name:       "com.example.MyType",
-			Properties: map[string]interface{}{"key": "value", "num": int32(42)},
+			Name:   "com.example.MyType",
+			Fields: map[string]interface{}{"key": "value", "num": int32(42)},
 		}
 		var buf bytes.Buffer
 		err := serializer.write(source, &buf)
@@ -554,18 +554,18 @@ func TestProviderDefinedTypeSerialization(t *testing.T) {
 		pdt, ok := result.(*ProviderDefinedType)
 		assert.True(t, ok)
 		assert.Equal(t, source.Name, pdt.Name)
-		assert.Equal(t, source.Properties["key"], pdt.Properties["key"])
-		assert.Equal(t, source.Properties["num"], pdt.Properties["num"])
+		assert.Equal(t, source.Fields["key"], pdt.Fields["key"])
+		assert.Equal(t, source.Fields["num"], pdt.Fields["num"])
 	})
 
 	t.Run("round-trip nested PDT", func(t *testing.T) {
 		inner := &ProviderDefinedType{
-			Name:       "com.example.Inner",
-			Properties: map[string]interface{}{"x": int32(1)},
+			Name:   "com.example.Inner",
+			Fields: map[string]interface{}{"x": int32(1)},
 		}
 		outer := &ProviderDefinedType{
-			Name:       "com.example.Outer",
-			Properties: map[string]interface{}{"child": inner},
+			Name:   "com.example.Outer",
+			Fields: map[string]interface{}{"child": inner},
 		}
 		var buf bytes.Buffer
 		err := serializer.write(outer, &buf)
@@ -577,10 +577,10 @@ func TestProviderDefinedTypeSerialization(t *testing.T) {
 		pdt, ok := result.(*ProviderDefinedType)
 		assert.True(t, ok)
 		assert.Equal(t, "com.example.Outer", pdt.Name)
-		child, ok := pdt.Properties["child"].(*ProviderDefinedType)
+		child, ok := pdt.Fields["child"].(*ProviderDefinedType)
 		assert.True(t, ok)
 		assert.Equal(t, "com.example.Inner", child.Name)
-		assert.Equal(t, int32(1), child.Properties["x"])
+		assert.Equal(t, int32(1), child.Fields["x"])
 	})
 
 	t.Run("empty name produces error", func(t *testing.T) {
@@ -603,8 +603,8 @@ func TestProviderDefinedTypeSerialization(t *testing.T) {
 			}, nil)
 
 		source := &ProviderDefinedType{
-			Name:       "com.example.MyType",
-			Properties: map[string]interface{}{"key": "value"},
+			Name:   "com.example.MyType",
+			Fields: map[string]interface{}{"key": "value"},
 		}
 		var buf bytes.Buffer
 		err := serializer.write(source, &buf)
@@ -621,8 +621,8 @@ func TestProviderDefinedTypeSerialization(t *testing.T) {
 
 	t.Run("no hydration without registry", func(t *testing.T) {
 		source := &ProviderDefinedType{
-			Name:       "com.example.MyType",
-			Properties: map[string]interface{}{"key": "value"},
+			Name:   "com.example.MyType",
+			Fields: map[string]interface{}{"key": "value"},
 		}
 		var buf bytes.Buffer
 		err := serializer.write(source, &buf)
diff --git a/gremlin-go/driver/gremlinlang.go b/gremlin-go/driver/gremlinlang.go
index dac9e64aa8..c6c36ad128 100644
--- a/gremlin-go/driver/gremlinlang.go
+++ b/gremlin-go/driver/gremlinlang.go
@@ -206,7 +206,7 @@ func (gl *GremlinLang) argAsString(arg interface{}) (string, error) {
 		name := reflect.ValueOf(v).Type().Name()
 		return fmt.Sprintf("%s.%s", strings.ToUpper(name), v), nil
 	case *ProviderDefinedType:
-		props := v.Properties
+		props := v.Fields
 		if props == nil {
 			props = map[string]interface{}{}
 		}
@@ -304,10 +304,10 @@ func (gl *GremlinLang) argAsString(arg interface{}) (string, error) {
 		// default behavior for a given Go type.
 		if gl.pdtRegistry != nil {
 			adapter := gl.pdtRegistry.GetAdapterByType(reflect.TypeOf(arg))
-			if adapter != nil && adapter.ToProperties != nil {
-				props, err := adapter.ToProperties(arg)
+			if adapter != nil && adapter.ToFields != nil {
+				props, err := adapter.ToFields(arg)
 				if err == nil {
-					pdt := &ProviderDefinedType{Name: adapter.TypeName, Properties: props}
+					pdt := &ProviderDefinedType{Name: adapter.TypeName, Fields: props}
 					return gl.argAsString(pdt)
 				}
 			}
diff --git a/gremlin-go/driver/gremlinlang_test.go b/gremlin-go/driver/gremlinlang_test.go
index 9ab9b218f0..7794770115 100644
--- a/gremlin-go/driver/gremlinlang_test.go
+++ b/gremlin-go/driver/gremlinlang_test.go
@@ -863,7 +863,7 @@ func Test_ConvertParametersToString(t *testing.T) {
 func Test_PDT_GremlinLang(t *testing.T) {
 	t.Run("basic PDT", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		pdt := &ProviderDefinedType{Name: "MyType", Properties: map[string]interface{}{"x": int32(1), "y": "hello"}}
+		pdt := &ProviderDefinedType{Name: "MyType", Fields: map[string]interface{}{"x": int32(1), "y": "hello"}}
 		gremlin := g.Inject(pdt).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("MyType",["x":1,"y":"hello"]))`
 		if gremlin != expected {
@@ -873,7 +873,7 @@ func Test_PDT_GremlinLang(t *testing.T) {
 
 	t.Run("empty PDT", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		pdt := &ProviderDefinedType{Name: "Empty", Properties: map[string]interface{}{}}
+		pdt := &ProviderDefinedType{Name: "Empty", Fields: map[string]interface{}{}}
 		gremlin := g.Inject(pdt).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("Empty",[:]))`
 		if gremlin != expected {
@@ -883,7 +883,7 @@ func Test_PDT_GremlinLang(t *testing.T) {
 
 	t.Run("PDT with special characters in name", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		pdt := &ProviderDefinedType{Name: `say"hello"`, Properties: map[string]interface{}{"v": int32(1)}}
+		pdt := &ProviderDefinedType{Name: `say"hello"`, Fields: map[string]interface{}{"v": int32(1)}}
 		gremlin := g.Inject(pdt).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("say\"hello\"",["v":1]))`
 		if gremlin != expected {
@@ -893,7 +893,7 @@ func Test_PDT_GremlinLang(t *testing.T) {
 
 	t.Run("PDT with backslash in name", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		pdt := &ProviderDefinedType{Name: `back\slash`, Properties: map[string]interface{}{"v": int32(1)}}
+		pdt := &ProviderDefinedType{Name: `back\slash`, Fields: map[string]interface{}{"v": int32(1)}}
 		gremlin := g.Inject(pdt).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("back\\slash",["v":1]))`
 		if gremlin != expected {
@@ -903,8 +903,8 @@ func Test_PDT_GremlinLang(t *testing.T) {
 
 	t.Run("nested PDT", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		inner := &ProviderDefinedType{Name: "Inner", Properties: map[string]interface{}{"v": int32(1)}}
-		outer := &ProviderDefinedType{Name: "Outer", Properties: map[string]interface{}{"inner": inner}}
+		inner := &ProviderDefinedType{Name: "Inner", Fields: map[string]interface{}{"v": int32(1)}}
+		outer := &ProviderDefinedType{Name: "Outer", Fields: map[string]interface{}{"inner": inner}}
 		gremlin := g.Inject(outer).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("Outer",["inner":PDT("Inner",["v":1])]))`
 		if gremlin != expected {
@@ -914,7 +914,7 @@ func Test_PDT_GremlinLang(t *testing.T) {
 
 	t.Run("PDT map keys sorted", func(t *testing.T) {
 		g := NewGraphTraversalSource(nil, nil)
-		pdt := &ProviderDefinedType{Name: "T", Properties: map[string]interface{}{"z": int32(3), "a": int32(1), "m": int32(2)}}
+		pdt := &ProviderDefinedType{Name: "T", Fields: map[string]interface{}{"z": int32(3), "a": int32(1), "m": int32(2)}}
 		gremlin := g.Inject(pdt).GremlinLang.GetGremlin()
 		expected := `g.inject(PDT("T",["a":1,"m":2,"z":3]))`
 		if gremlin != expected {
diff --git a/gremlin-go/driver/pdtRegistry.go b/gremlin-go/driver/pdtRegistry.go
index 1d14feec6c..8dd81bcb47 100644
--- a/gremlin-go/driver/pdtRegistry.go
+++ b/gremlin-go/driver/pdtRegistry.go
@@ -23,9 +23,9 @@ import "reflect"
 
 // PDTAdapter defines how to hydrate/dehydrate a provider-defined type.
 type PDTAdapter struct {
-	TypeName       string
-	FromProperties func(map[string]interface{}) (interface{}, error)
-	ToProperties   func(interface{}) (map[string]interface{}, error)
+	TypeName  string
+	FromFields func(map[string]interface{}) (interface{}, error)
+	ToFields   func(interface{}) (map[string]interface{}, error)
 }
 
 // PDTRegistry maps type names to their hydration adapters.
@@ -41,13 +41,13 @@ func NewPDTRegistry() *PDTRegistry {
 
 // RegisterFuncs registers hydration/dehydration functions for a type name.
 func (r *PDTRegistry) RegisterFuncs(typeName string, fromProps func(map[string]interface{}) (interface{}, error), toProps func(interface{}) (map[string]interface{}, error)) {
-	adapter := &PDTAdapter{TypeName: typeName, FromProperties: fromProps, ToProperties: toProps}
+	adapter := &PDTAdapter{TypeName: typeName, FromFields: fromProps, ToFields: toProps}
 	r.adaptersByName[typeName] = adapter
 }
 
 // RegisterFuncsWithType registers hydration/dehydration functions for a type name and associates a Go type for dehydration lookup.
 func (r *PDTRegistry) RegisterFuncsWithType(typeName string, targetType reflect.Type, fromProps func(map[string]interface{}) (interface{}, error), toProps func(interface{}) (map[string]interface{}, error)) {
-	adapter := &PDTAdapter{TypeName: typeName, FromProperties: fromProps, ToProperties: toProps}
+	adapter := &PDTAdapter{TypeName: typeName, FromFields: fromProps, ToFields: toProps}
 	r.adaptersByName[typeName] = adapter
 	r.adaptersByType[targetType] = adapter
 }
@@ -56,7 +56,7 @@ func (r *PDTRegistry) RegisterFuncsWithType(typeName string, targetType reflect.
 func (r *PDTRegistry) RegisterType(typeName string, targetType reflect.Type) {
 	r.adaptersByName[typeName] = &PDTAdapter{
 		TypeName: typeName,
-		FromProperties: func(props map[string]interface{}) (interface{}, error) {
+		FromFields: func(props map[string]interface{}) (interface{}, error) {
 			obj := reflect.New(targetType).Elem()
 			for i := 0; i < targetType.NumField(); i++ {
 				field := targetType.Field(i)
@@ -88,15 +88,15 @@ func (r *PDTRegistry) Hydrate(pdt *ProviderDefinedType) interface{} {
 	if !ok {
 		return pdt
 	}
-	hydratedProps := make(map[string]interface{}, len(pdt.Properties))
-	for k, v := range pdt.Properties {
+	hydratedFields := make(map[string]interface{}, len(pdt.Fields))
+	for k, v := range pdt.Fields {
 		if nested, ok := v.(*ProviderDefinedType); ok {
-			hydratedProps[k] = r.Hydrate(nested)
+			hydratedFields[k] = r.Hydrate(nested)
 		} else {
-			hydratedProps[k] = v
+			hydratedFields[k] = v
 		}
 	}
-	result, err := adapter.FromProperties(hydratedProps)
+	result, err := adapter.FromFields(hydratedFields)
 	if err != nil {
 		return pdt
 	}
diff --git a/gremlin-go/driver/pdtRegistry_test.go b/gremlin-go/driver/pdtRegistry_test.go
index 18a97566d8..cbd4ef1a98 100644
--- a/gremlin-go/driver/pdtRegistry_test.go
+++ b/gremlin-go/driver/pdtRegistry_test.go
@@ -33,14 +33,14 @@ func TestPDTRegistryRegisterFuncsAndHydrate(t *testing.T) {
 		return [2]int{props["x"].(int), props["y"].(int)}, nil
 	}, nil)
 
-	pdt := &ProviderDefinedType{Name: "x:Point", Properties: map[string]interface{}{"x": 1, "y": 2}}
+	pdt := &ProviderDefinedType{Name: "x:Point", Fields: map[string]interface{}{"x": 1, "y": 2}}
 	result := reg.Hydrate(pdt)
 	assert.Equal(t, [2]int{1, 2}, result)
 }
 
 func TestPDTRegistryNoAdapterReturnsRawPDT(t *testing.T) {
 	reg := NewPDTRegistry()
-	pdt := &ProviderDefinedType{Name: "x:Unknown", Properties: map[string]interface{}{"a": "b"}}
+	pdt := &ProviderDefinedType{Name: "x:Unknown", Fields: map[string]interface{}{"a": "b"}}
 	result := reg.Hydrate(pdt)
 	assert.Equal(t, pdt, result)
 }
@@ -51,7 +51,7 @@ func TestPDTRegistryAdapterErrorReturnsRawPDT(t *testing.T) {
 		return nil, errors.New("fail")
 	}, nil)
 
-	pdt := &ProviderDefinedType{Name: "x:Bad", Properties: map[string]interface{}{}}
+	pdt := &ProviderDefinedType{Name: "x:Bad", Fields: map[string]interface{}{}}
 	result := reg.Hydrate(pdt)
 	assert.Equal(t, pdt, result)
 }
@@ -65,8 +65,8 @@ func TestPDTRegistryNestedHydration(t *testing.T) {
 		return "outer:" + props["child"].(string), nil
 	}, nil)
 
-	inner := &ProviderDefinedType{Name: "x:Inner", Properties: map[string]interface{}{"val": "hi"}}
-	outer := &ProviderDefinedType{Name: "x:Outer", Properties: map[string]interface{}{"child": inner}}
+	inner := &ProviderDefinedType{Name: "x:Inner", Fields: map[string]interface{}{"val": "hi"}}
+	outer := &ProviderDefinedType{Name: "x:Outer", Fields: map[string]interface{}{"child": inner}}
 	result := reg.Hydrate(outer)
 	assert.Equal(t, "outer:hi!", result)
 }
@@ -81,7 +81,7 @@ func TestPDTRegistryRegisterType(t *testing.T) {
 	reg := NewPDTRegistry()
 	reg.RegisterType("x:Point", reflect.TypeOf(testPoint{}))
 
-	pdt := &ProviderDefinedType{Name: "x:Point", Properties: map[string]interface{}{"x": 3, "y": 4, "L": "label"}}
+	pdt := &ProviderDefinedType{Name: "x:Point", Fields: map[string]interface{}{"x": 3, "y": 4, "L": "label"}}
 	result := reg.Hydrate(pdt)
 	assert.Equal(t, testPoint{X: 3, Y: 4, L: "label"}, result)
 }
diff --git a/gremlin-go/driver/providerDefinedType.go b/gremlin-go/driver/providerDefinedType.go
index c2d969f67e..2a5a359892 100644
--- a/gremlin-go/driver/providerDefinedType.go
+++ b/gremlin-go/driver/providerDefinedType.go
@@ -26,25 +26,25 @@ import (
 
 // ProviderDefinedType represents a provider-defined type (PDT) in GraphBinary serialization.
 type ProviderDefinedType struct {
-	Name       string
-	Properties map[string]interface{}
+	Name   string
+	Fields map[string]interface{}
 }
 
 func (p *ProviderDefinedType) String() string {
-	return fmt.Sprintf("pdt[%s]%v", p.Name, p.Properties)
+	return fmt.Sprintf("pdt[%s]%v", p.Name, p.Fields)
 }
 
-// pdtWriter serializes a ProviderDefinedType as a fully-qualified string (name) followed by a fully-qualified map (properties).
+// pdtWriter serializes a ProviderDefinedType as a fully-qualified string (name) followed by a fully-qualified map (fields).
 func pdtWriter(value interface{}, w io.Writer, typeSerializer *graphBinaryTypeSerializer) error {
 	pdt := value.(*ProviderDefinedType)
 	if err := typeSerializer.write(pdt.Name, w); err != nil {
 		return err
 	}
-	if pdt.Properties == nil {
+	if pdt.Fields == nil {
 		return typeSerializer.write(map[interface{}]interface{}{}, w)
 	}
-	m := make(map[interface{}]interface{}, len(pdt.Properties))
-	for k, v := range pdt.Properties {
+	m := make(map[interface{}]interface{}, len(pdt.Fields))
+	for k, v := range pdt.Fields {
 		m[k] = v
 	}
 	return typeSerializer.write(m, w)
diff --git a/gremlin-go/driver/providerDefinedType_test.go b/gremlin-go/driver/providerDefinedType_test.go
index 91963456d8..8d0f394e24 100644
--- a/gremlin-go/driver/providerDefinedType_test.go
+++ b/gremlin-go/driver/providerDefinedType_test.go
@@ -28,8 +28,8 @@ import (
 func TestProviderDefinedType(t *testing.T) {
 	t.Run("String method", func(t *testing.T) {
 		pdt := &ProviderDefinedType{
-			Name:       "com.example.Test",
-			Properties: map[string]interface{}{"a": int32(1)},
+			Name:   "com.example.Test",
+			Fields: map[string]interface{}{"a": int32(1)},
 		}
 		assert.Contains(t, pdt.String(), "pdt[com.example.Test]")
 	})
diff --git a/gremlin-go/driver/traversal_test.go b/gremlin-go/driver/traversal_test.go
index bc842f1008..f26c241855 100644
--- a/gremlin-go/driver/traversal_test.go
+++ b/gremlin-go/driver/traversal_test.go
@@ -872,7 +872,7 @@ func TestProviderDefinedTypeTraversalAPIIntegration(t *testing.T) {
 		defer remote.Close()
 
 		g := Traversal_().With(remote)
-		pdt := &ProviderDefinedType{Name: "TestPoint", Properties: map[string]interface{}{"x": int32(1), "y": int32(2)}}
+		pdt := &ProviderDefinedType{Name: "TestPoint", Fields: map[string]interface{}{"x": int32(1), "y": int32(2)}}
 
 		results, err := g.Inject(pdt).ToList()
 		require.NoError(t, err)
@@ -881,8 +881,8 @@ func TestProviderDefinedTypeTraversalAPIIntegration(t *testing.T) {
 		result, ok := results[0].GetInterface().(*ProviderDefinedType)
 		require.True(t, ok, "expected *ProviderDefinedType, got %T", results[0].GetInterface())
 		assert.Equal(t, "TestPoint", result.Name)
-		assert.Equal(t, int32(1), result.Properties["x"])
-		assert.Equal(t, int32(2), result.Properties["y"])
+		assert.Equal(t, int32(1), result.Fields["x"])
+		assert.Equal(t, int32(2), result.Fields["y"])
 	})
 
 	t.Run("registry-based round-trip via typed struct", func(t *testing.T) {
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index 6b8bcbfea3..b4e4a788ed 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -907,7 +907,7 @@ class GremlinLang(object):
                 return tmp
 
         if isinstance(arg, ProviderDefinedType):
-            return f'PDT({self._arg_as_string(arg.name)},{self._process_dict(arg.properties)})'
+            return f'PDT({self._arg_as_string(arg.name)},{self._process_dict(arg.fields)})'
 
         if isinstance(arg, Vertex):
             return f'{self._arg_as_string(arg.id)}'
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/graph.py b/gremlin-python/src/main/python/gremlin_python/structure/graph.py
index 6160af3c10..5105013811 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/graph.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/graph.py
@@ -144,33 +144,33 @@ class Path(object):
 
 
 class ProviderDefinedType(object):
-    def __init__(self, name, properties):
+    def __init__(self, name, fields):
         if not name:
             raise ValueError("name cannot be null or empty")
         self._name = name
-        self._properties = dict(properties) if properties else {}
-        if any(not isinstance(k, str) for k in self._properties):
-            raise TypeError("ProviderDefinedType property keys must be strings")
+        self._fields = dict(fields) if fields else {}
+        if any(not isinstance(k, str) for k in self._fields):
+            raise TypeError("ProviderDefinedType field keys must be strings")
 
     @property
     def name(self):
         return self._name
 
     @property
-    def properties(self):
-        return self._properties
+    def fields(self):
+        return self._fields
 
     def __eq__(self, other):
-        return isinstance(other, ProviderDefinedType) and self._name == other._name and self._properties == other._properties
+        return isinstance(other, ProviderDefinedType) and self._name == other._name and self._fields == other._fields
 
     def __hash__(self):
         try:
-            return hash((self._name, frozenset(self._properties.items())))
+            return hash((self._name, frozenset(self._fields.items())))
         except TypeError:
             return hash(self._name)
 
     def __repr__(self):
-        return f"pdt[{self._name}]{self._properties}"
+        return f"pdt[{self._name}]{self._fields}"
 
 
 class ProviderDefinedTypeRegistry(object):
@@ -191,7 +191,7 @@ class ProviderDefinedTypeRegistry(object):
             }
 
     @classmethod
-    def build(cls):
+    def create(cls):
         """Create a registry populated by entry_points discovery.
 
         Providers register adapters via pyproject.toml:
@@ -228,9 +228,9 @@ class ProviderDefinedTypeRegistry(object):
         if adapter is None:
             return pdt
         try:
-            hydrated_props = {k: self.hydrate(v) if isinstance(v, ProviderDefinedType) else v
-                             for k, v in pdt.properties.items()}
-            return adapter['deserialize'](hydrated_props)
+            hydrated_fields = {k: self.hydrate(v) if isinstance(v, ProviderDefinedType) else v
+                               for k, v in pdt.fields.items()}
+            return adapter['deserialize'](hydrated_fields)
         except Exception as e:
             import logging
             logging.getLogger(__name__).warning(f"PDT hydration failed for '{pdt.name}': {e}")
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py
index cc5d9518fd..fa1ca12cb5 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py
@@ -181,7 +181,7 @@ class GraphBinaryReader(object):
         """Hydrate a ProviderDefinedType using a @provider_defined decorated class."""
         cls = _pdt_decorated_types[pdt.name]
         props = {}
-        for k, v in pdt.properties.items():
+        for k, v in pdt.fields.items():
             if isinstance(v, ProviderDefinedType) and v.name in _pdt_decorated_types:
                 props[k] = self._hydrate_decorated(v)
             elif self.pdt_registry is not None and isinstance(v, ProviderDefinedType):
@@ -958,7 +958,7 @@ class ProviderDefinedTypeIO(_GraphBinaryTypeIO):
     def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
         cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
         StringIO.dictify(obj.name, writer, to_extend)
-        MapIO.dictify(obj.properties, writer, to_extend)
+        MapIO.dictify(obj.fields, writer, to_extend)
         return to_extend
 
     @classmethod
@@ -968,5 +968,5 @@ class ProviderDefinedTypeIO(_GraphBinaryTypeIO):
     @classmethod
     def _read_pdt(cls, b, r):
         name = r.read_object(b)
-        properties = r.read_object(b)
-        return ProviderDefinedType(name, properties)
\ No newline at end of file
+        fields = r.read_object(b)
+        return ProviderDefinedType(name, fields)
\ No newline at end of file
diff --git a/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryV4.py b/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryV4.py
index 21b9cf7173..f7c8c7aa51 100644
--- a/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryV4.py
+++ b/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryV4.py
@@ -322,19 +322,19 @@ class TestGraphBinaryV4(object):
         result = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(pdt))
         assert isinstance(result, ProviderDefinedType)
         assert result.name == 'Point'
-        assert result.properties == {'x': 1, 'y': 2}
+        assert result.fields == {'x': 1, 'y': 2}
 
     def test_provider_defined_type_nested(self):
         inner = ProviderDefinedType('Address', {'street': 'Main'})
         outer = ProviderDefinedType('Person', {'name': 'Alice', 'address': inner})
         result = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(outer))
         assert result.name == 'Person'
-        assert result.properties['name'] == 'Alice'
-        assert isinstance(result.properties['address'], ProviderDefinedType)
-        assert result.properties['address'].name == 'Address'
+        assert result.fields['name'] == 'Alice'
+        assert isinstance(result.fields['address'], ProviderDefinedType)
+        assert result.fields['address'].name == 'Address'
 
     def test_provider_defined_type_null_field(self):
         pdt = ProviderDefinedType('NullableType', {'value': None, 'name': 'test'})
         result = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(pdt))
-        assert result.properties['value'] is None
-        assert result.properties['name'] == 'test'
+        assert result.fields['value'] is None
+        assert result.fields['name'] == 'test'
diff --git a/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py b/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
index 32deaa9840..b97b9905d8 100644
--- a/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
+++ b/gremlin-python/src/main/python/tests/unit/structure/io/test_provider_defined_type.py
@@ -105,7 +105,7 @@ class TestProviderDefinedTypeRegistry(object):
 class TestProviderDefinedTypeRegistryBuild(object):
 
     def test_build_returns_registry_with_no_entry_points(self):
-        registry = ProviderDefinedTypeRegistry.build()
+        registry = ProviderDefinedTypeRegistry.create()
         assert isinstance(registry, ProviderDefinedTypeRegistry)
 
     def test_build_loads_entry_point(self):
@@ -122,7 +122,7 @@ class TestProviderDefinedTypeRegistryBuild(object):
             else:
                 mock_entry_points.return_value = {'tinkerpop.pdt': [mock_ep]}
 
-            registry = ProviderDefinedTypeRegistry.build()
+            registry = ProviderDefinedTypeRegistry.create()
             assert "com.mock.Type" in registry._adapters_by_name
 
     def test_build_handles_failing_entry_point(self):
@@ -139,7 +139,7 @@ class TestProviderDefinedTypeRegistryBuild(object):
             else:
                 mock_entry_points.return_value = {'tinkerpop.pdt': [mock_ep]}
 
-            registry = ProviderDefinedTypeRegistry.build()
+            registry = ProviderDefinedTypeRegistry.create()
             assert isinstance(registry, ProviderDefinedTypeRegistry)
             assert len(registry._adapters_by_name) == 0
 
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java
index 03fafbd3f1..3f7f61dd30 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java
@@ -1287,8 +1287,8 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
             assertTrue(result instanceof ProviderDefinedType);
             final ProviderDefinedType r = (ProviderDefinedType) result;
             assertEquals("TestPoint", r.getName());
-            assertEquals(1, r.getProperties().get("x"));
-            assertEquals(2, r.getProperties().get("y"));
+            assertEquals(1, r.getFields().get("x"));
+            assertEquals(2, r.getFields().get("y"));
         } finally {
             cluster.close();
         }
@@ -1386,8 +1386,8 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
                     value instanceof ProviderDefinedType);
             final ProviderDefinedType pdt = (ProviderDefinedType) value;
             assertEquals("Point", pdt.getName());
-            assertEquals(3, pdt.getProperties().get("x"));
-            assertEquals(4, pdt.getProperties().get("y"));
+            assertEquals(3, pdt.getFields().get("x"));
+            assertEquals(4, pdt.getFields().get("y"));
         } finally {
             // cleanup
             client.submit("g.V().hasLabel('location').drop().iterate()", groovyRequestOptions).all().get();
@@ -1407,13 +1407,13 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
         // so the adapter's type name matches the server type name "Point".
         @Override public String typeName() { return "Point"; }
         @Override public Class<TestPoint> targetClass() { return TestPoint.class; }
-        @Override public Map<String, Object> toProperties(final TestPoint obj) {
+        @Override public Map<String, Object> toFields(final TestPoint obj) {
             final Map<String, Object> m = new HashMap<>();
             m.put("x", obj.x);
             m.put("y", obj.y);
             return m;
         }
-        @Override public TestPoint fromProperties(final Map<String, Object> props) {
+        @Override public TestPoint fromFields(final Map<String, Object> props) {
             return new TestPoint(((Number) props.get("x")).intValue(), ((Number) props.get("y")).intValue());
         }
     }
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java
index 28a81731e3..746184fab4 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java
@@ -278,8 +278,8 @@ public class GremlinServerSerializationIntegrateTest extends AbstractGremlinServ
         assertEquals(1, results.size());
         final ProviderDefinedType pdt = (ProviderDefinedType) results.get(0).getObject();
         assertEquals("Point", pdt.getName());
-        assertEquals(1, pdt.getProperties().get("x"));
-        assertEquals(2, pdt.getProperties().get("y"));
+        assertEquals(1, pdt.getFields().get("x"));
+        assertEquals(2, pdt.getFields().get("y"));
     }
 
     @Test
@@ -291,13 +291,13 @@ public class GremlinServerSerializationIntegrateTest extends AbstractGremlinServ
         assertEquals(1, results.size());
         final ProviderDefinedType person = (ProviderDefinedType) results.get(0).getObject();
         assertEquals("Person", person.getName());
-        assertEquals("Alice", person.getProperties().get("name"));
-        assertEquals(30, person.getProperties().get("age"));
+        assertEquals("Alice", person.getFields().get("name"));
+        assertEquals(30, person.getFields().get("age"));
 
-        final ProviderDefinedType address = (ProviderDefinedType) person.getProperties().get("address");
+        final ProviderDefinedType address = (ProviderDefinedType) person.getFields().get("address");
         assertEquals("Address", address.getName());
-        assertEquals("123 Main St", address.getProperties().get("street"));
-        assertEquals("Springfield", address.getProperties().get("city"));
+        assertEquals("123 Main St", address.getFields().get("street"));
+        assertEquals("Springfield", address.getFields().get("city"));
     }
 
     @Test
@@ -311,13 +311,13 @@ public class GremlinServerSerializationIntegrateTest extends AbstractGremlinServ
 
         final ProviderDefinedType p1 = (ProviderDefinedType) list.get(0);
         assertEquals("Point", p1.getName());
-        assertEquals(1, p1.getProperties().get("x"));
-        assertEquals(2, p1.getProperties().get("y"));
+        assertEquals(1, p1.getFields().get("x"));
+        assertEquals(2, p1.getFields().get("y"));
 
         final ProviderDefinedType p2 = (ProviderDefinedType) list.get(1);
         assertEquals("Point", p2.getName());
-        assertEquals(3, p2.getProperties().get("x"));
-        assertEquals(4, p2.getProperties().get("y"));
+        assertEquals(3, p2.getFields().get("x"));
+        assertEquals(4, p2.getFields().get("y"));
     }
 
     @Test
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
index 795712ff76..9204118184 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
@@ -55,18 +55,15 @@ public class GraphBinaryMessageSerializerV4 extends AbstractMessageSerializer<Gr
     private static final String MIME_TYPE = SerTokens.MIME_GRAPHBINARY_V4;
 
     /**
-     * Creates a new instance of the message serializer using the default type serializers.
+     * Creates a new instance of the message serializer using the default type serializers and
+     * an SPI-discovered {@link ProviderDefinedTypeRegistry} for automatic PDT hydration.
      */
     public GraphBinaryMessageSerializerV4() {
-        this(TypeSerializerRegistry.INSTANCE);
+        this(TypeSerializerRegistry.INSTANCE, ProviderDefinedTypeRegistry.create());
     }
 
     public GraphBinaryMessageSerializerV4(final TypeSerializerRegistry registry) {
-        reader = new GraphBinaryReader(registry);
-        writer = new GraphBinaryWriter(registry);
-        mapper = new GraphBinaryMapper(writer, reader);
-
-        requestSerializer = new RequestMessageSerializer();
+        this(registry, ProviderDefinedTypeRegistry.create());
     }
 
     public GraphBinaryMessageSerializerV4(final TypeSerializerRegistry registry, final ProviderDefinedTypeRegistry pdtRegistry) {
@@ -104,7 +101,7 @@ public class GraphBinaryMessageSerializerV4 extends AbstractMessageSerializer<Gr
         }
 
         final TypeSerializerRegistry registry = builder.create();
-        reader = new GraphBinaryReader(registry);
+        reader = new GraphBinaryReader(registry, ProviderDefinedTypeRegistry.create());
         writer = new GraphBinaryWriter(registry);
 
         requestSerializer = new RequestMessageSerializer();
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryPdtSpiAutoWiringTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryPdtSpiAutoWiringTest.java
new file mode 100644
index 0000000000..b356b82776
--- /dev/null
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryPdtSpiAutoWiringTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util.ser.binary;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
+import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4;
+import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Validates that the default {@link GraphBinaryMessageSerializerV4} auto-wires SPI-discovered
+ * {@link org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter} implementations
+ * for hydration without any user configuration.
+ */
+public class GraphBinaryPdtSpiAutoWiringTest {
+
+    private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+
+    @Test
+    public void shouldAutoHydrateSpiDiscoveredAdapterWithDefaultSerializer() throws SerializationException {
+        // The default no-arg constructor should pick up TestPointAdapter via SPI
+        final GraphBinaryMessageSerializerV4 serializer = new GraphBinaryMessageSerializerV4();
+
+        // Build a response containing a raw ProviderDefinedType matching the test adapter's typeName
+        final Map<String, Object> fields = new LinkedHashMap<>();
+        fields.put("x", 10);
+        fields.put("y", 20);
+        final ProviderDefinedType pdt = new ProviderDefinedType("test.Point", fields);
+
+        final ResponseMessage response = ResponseMessage.build()
+                .result(Collections.singletonList(pdt))
+                .create();
+
+        // Serialize and deserialize — the reader should hydrate the PDT into TestPoint
+        final ByteBuf buffer = serializer.writeHeader(response, allocator);
+        try {
+            final ResponseMessage deserialized = serializer.readChunk(buffer, true);
+            assertNotNull(deserialized.getResult().getData());
+            assertEquals(1, deserialized.getResult().getData().size());
+
+            final Object result = deserialized.getResult().getData().get(0);
+            assertTrue("Expected hydrated TestPoint but got: " + result.getClass().getName(),
+                    result instanceof TestPointAdapter.TestPoint);
+
+            final TestPointAdapter.TestPoint point = (TestPointAdapter.TestPoint) result;
+            assertEquals(10, point.x);
+            assertEquals(20, point.y);
+        } finally {
+            buffer.release();
+        }
+    }
+
+    @Test
+    public void shouldAutoHydrateSpiDiscoveredAdapterAfterConfigure() throws SerializationException {
+        // Verify configure() also includes the SPI registry
+        final GraphBinaryMessageSerializerV4 serializer = new GraphBinaryMessageSerializerV4();
+        serializer.configure(Collections.emptyMap(), Collections.emptyMap());
+
+        final Map<String, Object> fields = new LinkedHashMap<>();
+        fields.put("x", 5);
+        fields.put("y", 15);
+        final ProviderDefinedType pdt = new ProviderDefinedType("test.Point", fields);
+
+        final ResponseMessage response = ResponseMessage.build()
+                .result(Collections.singletonList(pdt))
+                .create();
+
+        final ByteBuf buffer = serializer.writeHeader(response, allocator);
+        try {
+            final ResponseMessage deserialized = serializer.readChunk(buffer, true);
+            final Object result = deserialized.getResult().getData().get(0);
+            assertTrue("Expected hydrated TestPoint after configure()", result instanceof TestPointAdapter.TestPoint);
+        } finally {
+            buffer.release();
+        }
+    }
+
+    @Test
+    public void shouldUseExplicitPdtRegistryOverSpiDefault() throws SerializationException {
+        // An empty registry should NOT hydrate since it has no adapters registered
+        final ProviderDefinedTypeRegistry emptyRegistry = ProviderDefinedTypeRegistry.empty();
+        final GraphBinaryMessageSerializerV4 serializer = new GraphBinaryMessageSerializerV4(
+                TypeSerializerRegistry.INSTANCE, emptyRegistry);
+
+        final Map<String, Object> fields = new LinkedHashMap<>();
+        fields.put("x", 1);
+        fields.put("y", 2);
+        final ProviderDefinedType pdt = new ProviderDefinedType("test.Point", fields);
+
+        final ResponseMessage response = ResponseMessage.build()
+                .result(Collections.singletonList(pdt))
+                .create();
+
+        final ByteBuf buffer = serializer.writeHeader(response, allocator);
+        try {
+            final ResponseMessage deserialized = serializer.readChunk(buffer, true);
+            final Object result = deserialized.getResult().getData().get(0);
+            // With empty registry, should remain as raw ProviderDefinedType (no hydration)
+            assertTrue("Expected raw ProviderDefinedType with explicit empty registry but got: " + result.getClass().getName(),
+                    result instanceof ProviderDefinedType);
+        } finally {
+            buffer.release();
+        }
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriterPdtTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java
similarity index 77%
rename from gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriterPdtTest.java
rename to gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java
index 863e2559da..2a3fa78d4f 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriterPdtTest.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java
@@ -16,11 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.structure.io.binary;
+package org.apache.tinkerpop.gremlin.util.ser.binary;
 
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.structure.io.Buffer;
+import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefined;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
+import org.apache.tinkerpop.gremlin.util.ser.NettyBufferFactory;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -35,6 +39,8 @@ public class GraphBinaryWriterPdtTest {
 
     private static final GraphBinaryReader reader = new GraphBinaryReader();
     private static final GraphBinaryWriter writer = new GraphBinaryWriter();
+    private static final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+    private static final NettyBufferFactory bufferFactory = new NettyBufferFactory();
 
     @ProviderDefined
     static class TestPoint {
@@ -53,19 +59,19 @@ public class GraphBinaryWriterPdtTest {
 
     @Test
     public void shouldAutoConvertAnnotatedObjectToPdt() throws IOException {
-        final Buffer buffer = HeapBuffer.allocate(1024);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         writer.write(new TestPoint(1, 2), buffer);
         buffer.readerIndex(0);
 
         final ProviderDefinedType result = reader.read(buffer);
         assertEquals("TestPoint", result.getName());
-        assertEquals(1, result.getProperties().get("x"));
-        assertEquals(2, result.getProperties().get("y"));
+        assertEquals(1, result.getFields().get("x"));
+        assertEquals(2, result.getFields().get("y"));
     }
 
     @Test
     public void shouldThrowActionableMessageForUnannotatedType() {
-        final Buffer buffer = HeapBuffer.allocate(1024);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         final IOException ex = assertThrows(IOException.class, () -> writer.write(new UnannotatedType(), buffer));
         assertTrue(ex.getMessage().contains("@ProviderDefined"));
         assertTrue(ex.getMessage().contains("UnannotatedType"));
@@ -78,7 +84,7 @@ public class GraphBinaryWriterPdtTest {
         props.put("y", 2);
         final ProviderDefinedType pdt = new ProviderDefinedType("TestPoint", props);
 
-        final Buffer buffer = HeapBuffer.allocate(1024);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         writer.write(pdt, buffer);
         buffer.readerIndex(0);
 
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TestPointAdapter.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TestPointAdapter.java
new file mode 100644
index 0000000000..6a352f274c
--- /dev/null
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TestPointAdapter.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util.ser.binary;
+
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Test-only adapter registered via META-INF/services for SPI auto-wiring validation.
+ */
+public class TestPointAdapter implements ProviderDefinedTypeAdapter<TestPointAdapter.TestPoint> {
+
+    public static class TestPoint {
+        public final int x;
+        public final int y;
+
+        public TestPoint(final int x, final int y) {
+            this.x = x;
+            this.y = y;
+        }
+    }
+
+    @Override
+    public String typeName() {
+        return "test.Point";
+    }
+
+    @Override
+    public Class<TestPoint> targetClass() {
+        return TestPoint.class;
+    }
+
+    @Override
+    public Map<String, Object> toFields(final TestPoint obj) {
+        final Map<String, Object> fields = new LinkedHashMap<>();
+        fields.put("x", obj.x);
+        fields.put("y", obj.y);
+        return fields;
+    }
+
+    @Override
+    public TestPoint fromFields(final Map<String, Object> fields) {
+        return new TestPoint(((Number) fields.get("x")).intValue(), ((Number) fields.get("y")).intValue());
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializerTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/ProviderDefinedTypeSerializerTest.java
similarity index 89%
rename from gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializerTest.java
rename to gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/ProviderDefinedTypeSerializerTest.java
index 76f373609d..822a835f3b 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/ProviderDefinedTypeSerializerTest.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/ProviderDefinedTypeSerializerTest.java
@@ -16,15 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.structure.io.binary.types;
+package org.apache.tinkerpop.gremlin.util.ser.binary.types;
 
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.structure.io.Buffer;
 import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
 import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
-import org.apache.tinkerpop.gremlin.structure.io.binary.HeapBuffer;
+import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
+import org.apache.tinkerpop.gremlin.util.ser.NettyBufferFactory;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -42,9 +44,11 @@ public class ProviderDefinedTypeSerializerTest {
 
     private static final GraphBinaryReader reader = new GraphBinaryReader();
     private static final GraphBinaryWriter writer = new GraphBinaryWriter();
+    private static final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+    private static final NettyBufferFactory bufferFactory = new NettyBufferFactory();
 
     private Buffer writeAndRead(final Object value) throws IOException {
-        final Buffer buffer = HeapBuffer.allocate(1024);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         writer.write(value, buffer);
         buffer.readerIndex(0);
         return buffer;
@@ -120,8 +124,7 @@ public class ProviderDefinedTypeSerializerTest {
 
     @Test(expected = IOException.class)
     public void shouldThrowOnEmptyNameDuringRead() throws IOException {
-        // Manually write a PDT with empty name to trigger the validation
-        final Buffer buffer = HeapBuffer.allocate(256);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         // Write type code for COMPOSITE_PDT
         buffer.writeByte(0xF0);
         // Write value_flag = 0 (not null)
@@ -141,7 +144,7 @@ public class ProviderDefinedTypeSerializerTest {
 
     @Test(expected = IOException.class)
     public void shouldThrowOnNonStringKeyInPropertiesMap() throws IOException {
-        final Buffer buffer = HeapBuffer.allocate(256);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         // Write type code for COMPOSITE_PDT (0xF0), value_flag 0
         buffer.writeByte(0xF0);
         buffer.writeByte(0x00);
@@ -170,7 +173,7 @@ public class ProviderDefinedTypeSerializerTest {
 
     @Test
     public void shouldHandleNullPdt() throws IOException {
-        final Buffer buffer = HeapBuffer.allocate(64);
+        final Buffer buffer = bufferFactory.create(allocator.buffer());
         writer.write(null, buffer);
         buffer.readerIndex(0);
         final Object result = reader.read(buffer);
@@ -188,18 +191,18 @@ public class ProviderDefinedTypeSerializerTest {
             public Class<Map<String, Object>> targetClass() { return (Class) Map.class; }
 
             @Override
-            public Map<String, Object> fromProperties(final Map<String, Object> properties) {
+            public Map<String, Object> fromFields(final Map<String, Object> properties) {
                 final Map<String, Object> result = new LinkedHashMap<>(properties);
                 result.put("hydrated", true);
                 return result;
             }
 
             @Override
-            public Map<String, Object> toProperties(final Map<String, Object> value) { return value; }
+            public Map<String, Object> toFields(final Map<String, Object> value) { return value; }
         });
 
         final GraphBinaryReader hydratingReader = new GraphBinaryReader(
-                org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry.INSTANCE, pdtRegistry);
+                TypeSerializerRegistry.INSTANCE, pdtRegistry);
 
         final Map<String, Object> props = new LinkedHashMap<>();
         props.put("x", 1);
diff --git a/gremlin-util/src/test/resources/META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter b/gremlin-util/src/test/resources/META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter
new file mode 100644
index 0000000000..408bc7aa62
--- /dev/null
+++ b/gremlin-util/src/test/resources/META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter
@@ -0,0 +1 @@
+org.apache.tinkerpop.gremlin.util.ser.binary.TestPointAdapter