You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by va...@apache.org on 2023/07/21 22:51:22 UTC

[tinkerpop] branch master updated: Native transactions implementation (#2087)

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

valentyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new 127bf02c84 Native transactions implementation (#2087)
127bf02c84 is described below

commit 127bf02c84618aed5777d115dfc8780aed17e55d
Author: Valentyn Kahamlyk <vk...@users.noreply.github.com>
AuthorDate: Fri Jul 21 15:51:16 2023 -0700

    Native transactions implementation (#2087)
    
    Performance test results
    
    Machine: EC2 c5.8xlarge
    Neo4jGraph run on tmpfs
    
    All times in milliseconds.
    Scenarios tested against Kelvin Lawrence's air-routes-latest data.
    See https://github.com/Bit-Quill/tinkerpop/blob/ken/txntester/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/Tester.java for more details on each Scenario
    | Scenario | TinkerGraph | TinkerTransactionGraph | Neo4jGraph | TinkerGraph |
    | -------- | -------- | -------- | -------- | -------- |
    | Read the graph with both() | 25 500 | 111 087 | 39 118 | 23 528 |
    | Drop all vertices based on descending centrality | 260 | 309 | 1 011 | 134 |
    | Drop all vertices in random order | 228 | 298 | 806 | 75 |
    | Drop all edges in random order | 940 | 1101 | 3 003 | 677 |
    | Move edges to different vertices randomly | 9 453 | 10 572 | 13 976 | 9 246 |
    | Update properties on random elements | 47 424 | 50 057 | 49 820 | 47 542 |
    | Add properties to vertices with MergeV | 73 | 96 | 138 | 78 |
    | Upsert edges with MergeE | 11 037 | 16 229 | 18 532 | 10 602 |
    | Add edges with a power law distribution to vertices | 390 | 2 934 | 3 465 | 363 |
    | Rollback edges after attempting to add them | N/A | 2 576 | 2680 | N/A |
    | Rollback vertices after attempting to drop them | N/A | 354 | 250 | N/A |
    | Move edges while committing half and rollbacking half | N/A | 9 838 | 12 514 | N/A |
    | Add edge between nodes that are two degrees away | 40 482 | 281 893 | 25 505 | 41 017 |
    | Drop edges from the most central vertices | 18 570 | 70 889 | 32 756 | 18 267 |
    
    ---------
    
    Co-authored-by: Ken Hu <10...@users.noreply.github.com>
    Co-authored-by: Yang Xia <55...@users.noreply.github.com>
---
 docker/gremlin-server/docker-entrypoint.sh         |  17 -
 .../gremlin-server-integration-krb5.yaml           |   4 +-
 .../gremlin-server-integration-secure.yaml         |   4 +-
 .../gremlin-server/gremlin-server-integration.yaml |   3 +-
 docker/gremlin-test-server/Dockerfile              |   2 +-
 .../dev/developer/development-environment.asciidoc |   3 +-
 docs/src/reference/gremlin-applications.asciidoc   |  26 +
 .../reference/implementations-tinkergraph.asciidoc |  86 +++
 ...implementations-tinkertransactiongraph.asciidoc |  57 ++
 docs/src/upgrade/release-3.7.x.asciidoc            | 124 ++++
 .../process/traversal/TraversalStrategies.java     |   2 +-
 .../gremlin/structure/io/GraphMigrator.java        |   2 +-
 .../gremlin/structure/service/Service.java         |   3 +-
 .../gremlin/structure/util/Attachable.java         |   9 +-
 .../gremlin/structure/util/CloseableIterator.java  |   4 +-
 .../gremlin/structure/util/ElementHelper.java      |   7 +-
 .../CollectionFactory.java => CollectionUtil.java} |  29 +-
 .../gremlin/util/{tools => }/MultiMap.java         |   2 +-
 .../gremlin/process/traversal/CompareTest.java     |   7 +-
 .../gremlin/process/traversal/OrderTest.java       |   6 +-
 .../dsl/graph/GraphTraversalSourceTest.java        |   6 +-
 .../traversal/dsl/graph/GraphTraversalTest.java    |   6 +-
 .../traversal/step/map/MergeEdgeStepTest.java      |  24 +-
 .../traversal/step/map/MergeVertexStepTest.java    |  36 +-
 .../tinkerpop/gremlin/util/CollectionUtilTest.java |  73 ++
 .../gremlin/util/{tools => }/MultiMapTest.java     |   2 +-
 .../GraphTraversalTransactionTests.cs              |  45 +-
 gremlin-go/driver/cucumber/cucumberSteps_test.go   |   8 +-
 .../ast/VarAsBindingASTTransformation.groovy       |   7 +-
 .../test/integration/traversal-test.js             |   4 +-
 .../conf/gremlin-server-transaction.yaml           |  35 +-
 .../conf/tinkertransactiongraph-empty.properties   |  18 +
 .../conf/tinkertransactiongraph-service.properties |  22 +
 gremlin-server/pom.xml                             | 122 ----
 .../AbstractGremlinServerIntegrationTest.java      |  26 +-
 .../gremlin/server/GremlinDriverIntegrateTest.java |  10 +-
 .../server/GremlinServerHttpIntegrateTest.java     |   6 +-
 .../gremlin/server/GremlinServerIntegrateTest.java |   6 +-
 .../server/GremlinServerSessionIntegrateTest.java  |   9 +-
 .../server/GremlinSessionTxIntegrateTest.java      |  14 +-
 .../server/util/CheckedGraphManagerTest.java       |   2 +-
 .../server/util/DefaultGraphManagerTest.java       |  17 +-
 .../gremlin/server/gremlin-server-integration.yaml |   4 +-
 .../src/test/scripts/generate-all.groovy           |  11 +-
 .../src/test/scripts/test-server-start.groovy      |  13 +-
 .../tinkertransactiongraph-empty.properties        |  22 +
 .../algorithm/generator/AbstractGeneratorTest.java |  16 +-
 .../process/traversal/step/OrderabilityTest.java   |  12 +-
 .../process/traversal/step/map/MergeEdgeTest.java  |   2 +-
 .../traversal/step/map/MergeVertexTest.java        |   2 +-
 .../gremlin/structure/FeatureSupportTest.java      |   3 +-
 .../tinkerpop/gremlin/structure/GraphTest.java     |  22 +-
 .../gremlin/structure/StructureStandardSuite.java  |   1 +
 .../structure/TransactionMultiThreadedTest.java    | 674 +++++++++++++++++++
 .../gremlin/structure/TransactionTest.java         |  14 +-
 .../gremlin/structure/VertexPropertyTest.java      |  28 +-
 .../tinkerpop/gremlin/structure/VertexTest.java    |   2 +-
 .../gremlin/structure/io/IoCustomTest.java         |   2 +
 .../tinkerpop/gremlin/structure/io/IoTest.java     |   2 +
 .../util/ser/GraphSONMessageSerializerV1Test.java  |   2 +-
 .../util/ser/GraphSONMessageSerializerV3Test.java  |   6 +-
 .../GraphSONUntypedMessageSerializerV1Test.java    |   4 +-
 .../gremlin/neo4j/structure/Neo4jGraph.java        |   5 +
 .../process/computer/TinkerGraphComputer.java      |   5 +-
 .../process/computer/TinkerGraphComputerView.java  |   5 +-
 .../process/computer/TinkerWorkerPool.java         |   5 +-
 .../traversal/step/map/TinkerCountGlobalStep.java  |   7 +-
 .../traversal/step/sideEffect/TinkerGraphStep.java |  15 +-
 .../optimization/TinkerGraphCountStrategy.java     |   6 +-
 .../services/TinkerDegreeCentralityFactory.java    |   6 +-
 .../services/TinkerServiceRegistry.java            |  14 +-
 .../services/TinkerTextSearchFactory.java          |   6 +-
 .../{TinkerGraph.java => AbstractTinkerGraph.java} | 490 +++++---------
 .../tinkergraph/structure/AbstractTinkerIndex.java | 134 ++++
 .../gremlin/tinkergraph/structure/TinkerEdge.java  | 100 ++-
 .../tinkergraph/structure/TinkerElement.java       |  12 +
 .../structure/TinkerElementContainer.java          | 245 +++++++
 .../tinkergraph/structure/TinkerFactory.java       |  84 ++-
 .../gremlin/tinkergraph/structure/TinkerGraph.java | 493 +++-----------
 .../tinkergraph/structure/TinkerHelper.java        | 175 ++---
 .../gremlin/tinkergraph/structure/TinkerIndex.java |  60 +-
 .../tinkergraph/structure/TinkerIndexHelper.java   |  72 ++
 .../tinkergraph/structure/TinkerProperty.java      |  14 +-
 .../tinkergraph/structure/TinkerTransaction.java   | 232 +++++++
 .../structure/TinkerTransactionGraph.java          | 515 +++++++++++++++
 .../structure/TinkerTransactionalIndex.java        | 239 +++++++
 .../tinkergraph/structure/TinkerVertex.java        | 104 ++-
 .../structure/TinkerVertexProperty.java            |  32 +-
 .../TinkerGraphAllowNullFeatureTest.java           |   8 +-
 .../TinkerGraphComputerFeatureTest.java            |   8 +-
 .../tinkergraph/TinkerGraphFeatureTest.java        |   2 +-
 .../gremlin/tinkergraph/TinkerGraphWorld.java      | 148 -----
 ...inkerTransactionGraphAllowNullFeatureTest.java} |  15 +-
 ...TinkerTransactionGraphComputerFeatureTest.java} |  15 +-
 ...java => TinkerTransactionGraphFeatureTest.java} |   6 +-
 .../TinkerTransactionGraphProvider.java            | 212 ++++++
 .../tinkerpop/gremlin/tinkergraph/TinkerWorld.java | 254 +++++++
 .../structure/TinkerGraphServiceTest.java          |   2 +-
 ...inkerTransactionGraphStructureStandardTest.java |  33 +
 .../structure/TinkerTransactionGraphTest.java      | 734 +++++++++++++++++++++
 .../io.cucumber.core.backend.ObjectFactory         |   5 +-
 101 files changed, 4723 insertions(+), 1531 deletions(-)

diff --git a/docker/gremlin-server/docker-entrypoint.sh b/docker/gremlin-server/docker-entrypoint.sh
index f11348ec4f..fa87baf212 100644
--- a/docker/gremlin-server/docker-entrypoint.sh
+++ b/docker/gremlin-server/docker-entrypoint.sh
@@ -23,21 +23,17 @@ cp /opt/test/scripts/* ${TINKERPOP_HOME}/scripts
 
 IP=$(hostname -i)
 
-INCLUDE_NEO4J=
-
 function usage {
   echo -e "\nUsage: `basename $0` <version> [OPTIONS]" \
           "\nStart Gremlin Server instances that match the Maven integration test environment." \
           "\n\nOptions are:\n" \
           "\n\t<version> This value is optional and if unspecified will build the current version" \
-          "\n\t-n, --neo4j              include Neo4j to make transactions testable" \
           "\n\t-h, --help               show this message" \
           "\n"
 }
 
 while [ ! -z "$1" ]; do
   case "$1" in
-    -n | --neo4j ) INCLUDE_NEO4J=true; shift ;;
     -h | --help ) usage; exit 0 ;;
     *) usage 1>&2; exit 1 ;;
   esac
@@ -51,10 +47,6 @@ echo "ws://${IP}:45940/gremlin with anonymous access"
 echo "ws://${IP}:45941/gremlin with basic authentication (stephen/password)"
 echo "ws://${IP}:45942/gremlin with kerberos authentication (stephen/password)"
 echo
-if [ ! -z "${INCLUDE_NEO4J}" ]; then
-  echo Installing Neo4j to the environment: transactions are testable on port 45940
-  echo
-fi
 echo "See docker/gremlin-server/docker-entrypoints.sh for transcripts per GLV."
 echo "#############################################################################"
 
@@ -65,15 +57,6 @@ java -version
 dos2unix /opt/gremlin-server/bin/gremlin-server.sh
 dos2unix /opt/gremlin-server/bin/gremlin-server.conf
 
-# dynamically installs Neo4j libraries so that we can test variants with transactions,
-# but only only port 45940 is configured with the neo4j graph as the neo4j-empty.properties
-# is statically pointing at a temp directory and that space can only be accessed by one
-# graph at a time
-if [ ! -z "${INCLUDE_NEO4J}" ]; then
-  sed -i 's/graphs: {/graphs: {\n  tx: conf\/neo4j-empty.properties,/' ${TINKERPOP_HOME}/conf/gremlin-server-integration.yaml
-  /opt/gremlin-server/bin/gremlin-server.sh install org.apache.tinkerpop neo4j-gremlin ${GREMLIN_SERVER_VERSION}
-fi
-
 /opt/gremlin-server/bin/gremlin-server.sh ${TINKERPOP_HOME}/conf/gremlin-server-integration.yaml &
 
 /opt/gremlin-server/bin/gremlin-server.sh ${TINKERPOP_HOME}/conf/gremlin-server-integration-secure.yaml &
diff --git a/docker/gremlin-server/gremlin-server-integration-krb5.yaml b/docker/gremlin-server/gremlin-server-integration-krb5.yaml
index fa71763536..e2ca7f0869 100644
--- a/docker/gremlin-server/gremlin-server-integration-krb5.yaml
+++ b/docker/gremlin-server/gremlin-server-integration-krb5.yaml
@@ -24,7 +24,9 @@ graphs: {
   modern: conf/tinkergraph-empty.properties,
   crew: conf/tinkergraph-empty.properties,
   grateful: conf/tinkergraph-empty.properties,
-  sink: conf/tinkergraph-empty.properties}
+  sink: conf/tinkergraph-empty.properties,
+  tx: conf/tinkertransactiongraph-empty.properties
+}
 scriptEngines: {
   gremlin-lang : {},
   gremlin-groovy: {
diff --git a/docker/gremlin-server/gremlin-server-integration-secure.yaml b/docker/gremlin-server/gremlin-server-integration-secure.yaml
index 77e56d07b8..09776d105d 100644
--- a/docker/gremlin-server/gremlin-server-integration-secure.yaml
+++ b/docker/gremlin-server/gremlin-server-integration-secure.yaml
@@ -24,7 +24,9 @@ graphs: {
   modern: conf/tinkergraph-empty.properties,
   crew: conf/tinkergraph-empty.properties,
   grateful: conf/tinkergraph-empty.properties,
-  sink: conf/tinkergraph-empty.properties}
+  sink: conf/tinkergraph-empty.properties,
+  tx: conf/tinkertransactiongraph-empty.properties
+}
 scriptEngines: {
   gremlin-lang : {},
   gremlin-groovy: {
diff --git a/docker/gremlin-server/gremlin-server-integration.yaml b/docker/gremlin-server/gremlin-server-integration.yaml
index 74b4ba4953..d039479508 100644
--- a/docker/gremlin-server/gremlin-server-integration.yaml
+++ b/docker/gremlin-server/gremlin-server-integration.yaml
@@ -26,7 +26,8 @@ graphs: {
   modern: conf/tinkergraph-service.properties,
   crew: conf/tinkergraph-service.properties,
   grateful: conf/tinkergraph-service.properties,
-  sink: conf/tinkergraph-service.properties
+  sink: conf/tinkergraph-service.properties,
+  tx: conf/tinkertransactiongraph-service.properties
 }
 scriptEngines: {
   gremlin-lang : {},
diff --git a/docker/gremlin-test-server/Dockerfile b/docker/gremlin-test-server/Dockerfile
index 4053a7246d..6802dcaaff 100644
--- a/docker/gremlin-test-server/Dockerfile
+++ b/docker/gremlin-test-server/Dockerfile
@@ -37,5 +37,5 @@ ENV GREMLIN_SERVER_VERSION=$GREMLIN_SERVER
 
 EXPOSE 45940-45942 4588
 
-ENTRYPOINT ["/opt/docker-entrypoint.sh", "-n"]
+ENTRYPOINT ["/opt/docker-entrypoint.sh"]
 CMD []
diff --git a/docs/src/dev/developer/development-environment.asciidoc b/docs/src/dev/developer/development-environment.asciidoc
index 21c4c19d04..4b653e8dae 100644
--- a/docs/src/dev/developer/development-environment.asciidoc
+++ b/docs/src/dev/developer/development-environment.asciidoc
@@ -486,7 +486,8 @@ mvn -Dmaven.javadoc.skip=true --projects tinkergraph-gremlin test
 * Check the effective `pom.xml`: `mvn -pl gremlin-python -Pglv-python help:effective-pom -Doutput=withProfilePom.xml`
 * Deploy JavaDocs/AsciiDocs: `bin/publish-docs.sh svn-username`
 * Integration Tests: `mvn verify -DskipIntegrationTests=false`
-** Execute with the `-DincludeNeo4j` option to include transactional tests.
+** Execute with the `-DincludeNeo4j` option to include neo4j-gremlin tests. As of 3.7.0, transactional tests on Gremlin Server, Gremlin Driver,
+and GLVs will run automatically with integration tests against the TinkerTransactionGraph.
 ** Execute with the `-DuseEpoll` option to try to use Netty native transport (works on Linux, but will fallback to Java NIO on other OS).
 * Benchmarks: `mvn verify -DskipBenchmarks=false`
 ** Reports are generated to the console and to `gremlin-tools/gremlin-benchmark/target/reports/benchmark`.
diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc
index b05999cb61..3b9164f076 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -2578,6 +2578,32 @@ simply retry the request later. In the event a client detects a `ResponseStatusC
 which is error code `596`, the client may choose to retry that request. Note that drivers do not have the ability to
 automatically retry and that it is up to the application to provide such logic.
 
+[[testing-remote-providers]]
+==== Testing Remote Providers
+
+As of 3.7.0, it is recommended to use TinkerTransactionGraph, the transactional TinkerGraph, for testing, as it provides
+transactional capabilities on top of exiting TinkerGraph functionalities.
+
+To use TinkerTransactionGraph remotely, start a Gremlin Server with the `gremlin-server-transaction.yaml` config file.
+
+[source,java]
+----
+Cluster cluster = Cluster.build("localhost").port(8182).create();
+Client client = cluster.connect();
+GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(client, "g"));
+GraphTraversalSource gtx = g.tx().begin();
+
+try {
+  Vertex marko = gtx.addV("person").property("name","marko").property("age",29).next();
+  Vertex lop = gtx.addV("software").property("name","lop").property("lang","java").next();
+  gtx.addE("created").from(marko).to(lop).property("weight",0.6d).iterate();
+
+  gtx.tx().commit();
+} catch (Exception ex) {
+  gtx.tx().rollback();
+}
+----
+
 [[gremlin-server-docker-image]]
 === Docker Image
 
diff --git a/docs/src/reference/implementations-tinkergraph.asciidoc b/docs/src/reference/implementations-tinkergraph.asciidoc
index a37633801c..771fe215fb 100644
--- a/docs/src/reference/implementations-tinkergraph.asciidoc
+++ b/docs/src/reference/implementations-tinkergraph.asciidoc
@@ -198,3 +198,89 @@ g = traversal().withEmbedded(graph)
 g.io("data/tinkerpop-crew.kryo").read().iterate()
 g.V().properties()
 ----
+
+=== Transactions
+
+As of 3.7.0, `TinkerGraph` includes optional transaction support and thread-safety through the `TinkerTransactionGraph`
+class. The default configuration of TinkerGraph remains non-transactional.
+
+==== Transaction Semantics
+
+TinkerTransactionGraph only has support for ThreadLocal transactions, so embedded graph transactions may not be fully
+supported. You can think of the transaction as belonging to a thread, any traversals executed within the same thread
+will share the same transaction even if you attempt to start a new transaction.
+
+TinkerTransactionGraph implements `read committed` isolation level, so it only guards against dirty reads.
+Additionally, it employs optimistic locking as its locking strategy. This reduces complexity in the design as
+there are fewer timeouts that the user needs to manage. However, a consequence of this approach is that a transaction
+will throw a `TransactionException` if two different transactions attempt to lock the same element (see "Best Practices"
+below).
+
+==== Use Cases
+
+These transaction semantics described above may not fit use cases for some production scenarios that require strict
+ACID-like transactions. Therefore, it is recommended that TinkerTransactionGraph be used as a Graph for test
+environments where you still require access to a Graph that supports transactions. TinkerTransactionGraph does fully
+support TinkerPop's `Transaction` interface which still makes it a useful Graph for exploring the Transaction API.
+
+Once you are ready to put your system into production then you can choose a Graph provider that has the
+transaction semantics that fit your application's needs.
+
+==== Best Practices
+
+Errors can occur before a transaction gets committed. Specifically for TinkerTransactionGraph, you may encounter many
+`TransactionException`s in a highly concurrent environment due its optimistic approach to locking. Users should follow
+the try-catch-rollback pattern described in the
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#transactions[transactions] section in combination with
+exponential backoff based retries to mitigate this issue.
+
+==== Performance Considerations
+
+While transactions impose minimal impact for mutating workloads, users should expect performance degradation for
+read-only work relative to the non-transactional configuration. However, its approach to locking
+(write-only, optimistic) and its in-memory nature, TinkerTransactionGraph is likely faster than other Graph
+implementations that support transactions.
+
+==== Examples
+
+Constructing a simple graph using TinkerTransactionGraph in Java is presented below:
+
+[source,java]
+----
+Graph graph = TinkerTransactionGraph.open();
+GraphTraversalSource gtx = graph.tx().begin();
+
+try {
+  Vertex marko = gtx.addV("person").property("name","marko").property("age",29).next();
+  Vertex lop = gtx.addV("software").property("name","lop").property("lang","java").next();
+  gtx.addE("created").from(marko).to(lop).property("weight",0.6d).iterate();
+
+  gtx.tx().commit();
+} catch (Exception ex) {
+  gtx.tx().rollback();
+}
+----
+
+The above Gremlin creates two vertices named "marko" and "lop" and connects them via a created-edge with a weight=0.6
+property. In case of any errors `rollback()` will be called and no changes will be performed.
+
+To use the embedded TinkerTransactionGraph in Gremlin Console:
+
+[gremlin-groovy]
+----
+gremlin> graph = TinkerTransactionGraph.open() <1>
+gremlin> g = traversal().withEmbedded(graph) <2>
+gremlin> g.addV('test').property('name','one')
+gremlin> g.tx().commit() <3>
+gremlin> g.V().valueMap()
+gremlin> g.addV('test').property('name','two') <4>
+gremlin> g.V().valueMap()
+gremlin> g.tx().rollback() <5>
+gremlin> g.V().valueMap()
+----
+
+<1> Open transactional graph.
+<2> Spawn a GraphTraversalSource with transactional graph.
+<3> Commit the add vertex operation
+<4> Add a second vertex without committing
+<5> Rollback the change
diff --git a/docs/src/reference/implementations-tinkertransactiongraph.asciidoc b/docs/src/reference/implementations-tinkertransactiongraph.asciidoc
new file mode 100644
index 0000000000..951adcdb4d
--- /dev/null
+++ b/docs/src/reference/implementations-tinkertransactiongraph.asciidoc
@@ -0,0 +1,57 @@
+////
+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.
+////
+[[tinkertransactiongraph-gremlin]]
+== TinkerTransactionGraph-Gremlin
+
+[source,xml]
+----
+<dependency>
+   <groupId>org.apache.tinkerpop</groupId>
+   <artifactId>tinkergraph-gremlin</artifactId>
+   <version>x.y.z</version>
+</dependency>
+----
+
+image:tinkerpop-character.png[width=100,float=left] TinkerTransactionGraph is a single machine, in-memory (with 
+optional persistence), transactional graph engine that provides both OLTP and OLAP functionality. It can be deployed with
+TinkerPop and serves as playground with transaction support. It is not suitable for production. 
+
+Main use case is testing enviroment with transaction support. In other cases better to use TinkerGraph.
+
+Constructing a simple graph using TinkerTransactionGraph in Java is presented below:
+
+[source,java]
+----
+Cluster cluster = Cluster.build("localhost").port(8182).create();
+Client client = cluster.connect();
+GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(client, "gttx"));
+GraphTraversalSource gtx = g.tx().begin();
+
+try {
+  Vertex marko = gtx.addV("person").property("name","marko").property("age",29).next();
+  Vertex lop = gtx.addV("software").property("name","lop").property("lang","java").next();
+  gtx.addE("created").from(marko).to(lop).property("weight",0.6d).iterate();
+
+  gtx.tx().commit();
+} catch (Exception ex) {
+  gtx.tx().rollback();
+}
+----
+
+The above Gremlin creates two vertices named "marko" and "lop" and connects them via a created-edge with a weight=0.6
+property. In case of any errors no changes will be performed.
+
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index 2b9bfc5dc3..a56fa02dcf 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -157,6 +157,130 @@ gremlin> g.V(1234).valueMap()
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2957[TINKERPOP-2957]
 
+==== TinkerGraph Transactions
+
+===== Introduction
+
+Previously, there was no reference implementation provided for the `Transaction` API as this feature wasn't supported by
+TinkerGraph. Users were instead directed towards the Neo4jGraph provided in `neo4j-gremlin` if they wanted to get access
+to a `Graph` implementation that supported transactions. Unfortunately, the maintenance around this plugin has largely
+been abandoned and is only compatible with Neo4j version 3.4, which reached end of life in March 2020.
+
+As of this version, we are introducing the transactional TinkerGraph, TinkerTransactionGraph, which is TinkerGraph with
+transaction capabilities. The TinkerTransactionGraph has `read committed` isolation level, which is the same as the
+Neo4jGraph provided in `neo4j-gremlin`. Only ThreadLocal transactions are implemented, therefore embedded graph
+transactions may not be fully supported. These transaction semantics may not fit the use case for some production
+scenarios that require strict ACID-like transactions. Therefore, it is recommended that TinkerTransactionGraph be used
+as a Graph for test environments where you still require support for transactions.
+
+===== Usage examples
+
+To use TinkerTransactionGraph remotely, start a Gremlin Server with the `gremlin-server-transaction.yaml` config file.
+
+[source,java]
+----
+GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using("localhost",8182,"g")); <1>
+
+GraphTraversalSource gtx = g.tx().begin(); <2>
+try {
+    gtx.addV('test1').iterate(); <3>
+    gtx.addV('test2').iterate(); <3>
+
+    gtx.tx().commit(); <4>
+} catch (Exception ex) {
+    gtx.tx().rollback(); <5>
+}
+----
+
+<1> Create connection to Gremlin Server with transaction enabled graph.
+<2> Spawn a GraphTraversalSource with opened transaction.
+<3> Make some updates to graph.
+<4> Commit all changes.
+<5> Rollback all changes on error.
+
+One can also use the remote TinkerTransactionGraph in Gremlin Console:
+
+[source,text]
+----
+gremlin> :remote connect tinkerpop.server conf/remote.yaml session <1>
+==>Configured localhost/127.0.0.1:8182-[2e70bf11-12f7-4dfe-8a5e-a3d57f0df304]
+gremlin> g = traversal().withRemote(DriverRemoteConnection.using("localhost",8182,"g"))
+==>graphtraversalsource[emptygraph[empty], standard]
+gremlin> gtx = g.tx().begin() <2>
+==>graphtraversalsource[emptygraph[empty], standard]
+gremlin> gtx.addV('test').property('name', 'one')
+==>v[0]
+gremlin> gtx.V().valueMap()
+==>[name:[one]]
+gremlin> g.V().valueMap()
+gremlin> gtx.tx().commit()
+==>null
+gremlin> g.V().valueMap() <3>
+==>[name:[one]]
+gremlin> g.V()
+==>v[0]
+gremlin> gtx = g.tx().begin() <4>
+==>graphtraversalsource[emptygraph[empty], standard]
+gremlin> gtx.addV('test').property('name', 'two')
+==>v[2]
+gremlin> gtx.V().valueMap()
+==>[name:[one]]
+==>[name:[two]]
+gremlin> g.V().valueMap()
+==>[name:[one]]
+gremlin> gtx.tx().rollback()
+==>null
+gremlin> g.V().valueMap() <5>
+==>[name:[one]]
+----
+
+<1> Open remote Console session and spawn remote graph traversal source for the empty TinkerTransactionGraph.
+<2> Spawn a GraphTraversalSource by opening a transaction.
+<3> The vertex is added in the remote graph until we commit the transaction (which automatically closes the transaction).
+<4> Spawn another GraphTraversalSource by opening a new transaction.
+<5> The second vertex will not bed added to the remote graph since we rolled back the change
+
+To use the embedded TinkerTransactionGraph in Gremlin Console:
+
+[source,text]
+----
+gremlin> graph = TinkerTransactionGraph.open() <1>
+==>tinkertransactiongraph[vertices:0 edges:0]
+gremlin> g = traversal().withEmbedded(graph) <2>
+==>graphtraversalsource[tinkertransactiongraph[vertices:0 edges:0], standard]
+gremlin> g.addV('test').property('name','one')
+==>v[0]
+gremlin> g.tx().commit() <3>
+==>null
+gremlin> g.V().valueMap()
+==>[name:[one]]
+gremlin> g.addV('test').property('name','two') <4>
+==>v[2]
+gremlin> g.V().valueMap()
+==>[name:[one]]
+==>[name:[two]]
+gremlin> g.tx().rollback() <5>
+==>null
+gremlin> g.V().valueMap()
+==>[name:[one]]
+----
+
+<1> Open transactional graph.
+<2> Spawn a GraphTraversalSource with transactional graph.
+<3> Commit the add vertex operation
+<4> Add a second vertex without committing
+<5> Rollback the change
+
+Note that all embedded TinkerTransactionGraph remains ThreadLocal transactions, meaning that all traversal sources spawn
+from the graph will operate within the same transaction scope.
+
+===== Limitations
+At this point, the transactional TinkerGraph comes with performance limitations and thus is only meant for testing
+and not for production use.
+
+Addition documentation can be found in <<transactions,Transactions>> section.
+
+
 ==== Properties on Elements
 
 ===== Introduction
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
index 01deea13ec..fc97198ac6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
@@ -44,7 +44,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrat
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
-import org.apache.tinkerpop.gremlin.util.tools.MultiMap;
+import org.apache.tinkerpop.gremlin.util.MultiMap;
 
 import java.io.Serializable;
 import java.util.ArrayList;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/GraphMigrator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/GraphMigrator.java
index 7c0a88579e..b8c5957072 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/GraphMigrator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/GraphMigrator.java
@@ -37,7 +37,7 @@ import static org.apache.tinkerpop.gremlin.structure.io.IoCore.gryo;
  */
 public final class GraphMigrator {
 
-    private GraphMigrator() {}
+    private GraphMigrator() { }
 
     /**
      * Use Gryo to pipe the data from one graph to another graph.  Uses readers and writers generated from each
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/Service.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/Service.java
index d211da9530..0062f67807 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/Service.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/Service.java
@@ -27,11 +27,10 @@ import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSe
 import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
 
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 
 /**
  * Service call with I input type and R return type. Services can return {@link Traverser}s or raw values (which will be
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java
index 701121f63e..bebd8f8ac9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java
@@ -197,8 +197,10 @@ public interface Attachable<V> {
             final Iterator<Edge> edgeIterator = hostVertex.edges(Direction.OUT, attachableEdge.get().label());
             while (edgeIterator.hasNext()) {
                 final Edge edge = edgeIterator.next();
-                if (ElementHelper.areEqual(edge, baseEdge))
+                if (ElementHelper.areEqual(edge, baseEdge)) {
+                    CloseableIterator.closeIterator(edgeIterator);
                     return Optional.of(edge);
+                }
             }
             return Optional.empty();
         }
@@ -271,8 +273,10 @@ public interface Attachable<V> {
                     final Edge edge = edgeIterator.next();
                     if (ElementHelper.areEqual(edge, propertyElement)) {
                         final Property property = edge.property(baseProperty.key());
-                        if (ElementHelper.areEqual(baseProperty, property))
+                        if (ElementHelper.areEqual(baseProperty, property)) {
+                            CloseableIterator.closeIterator(edgeIterator);
                             return Optional.of(property);
+                        }
                     }
                 }
                 return Optional.empty();
@@ -282,6 +286,7 @@ public interface Attachable<V> {
                     final VertexProperty vertexProperty = vertexPropertyIterator.next();
                     if (ElementHelper.areEqual(vertexProperty, baseProperty.element())) {
                         final Property property = vertexProperty.property(baseProperty.key());
+                        CloseableIterator.closeIterator(vertexPropertyIterator);
                         if (property.isPresent() && property.value().equals(baseProperty.value()))
                             return Optional.of(property);
                         else
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/CloseableIterator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/CloseableIterator.java
index 60f3d0b5fd..06a098459e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/CloseableIterator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/CloseableIterator.java
@@ -25,12 +25,12 @@ import java.io.Closeable;
 import java.util.Iterator;
 
 /**
- * An extension of {@code Iterator} that implements {@code Closeable} which allows a {@link Graph} implementation
+ * An extension of {@code Iterator} that implements {@code AutoCloseable} which allows a {@link Graph} implementation
  * that hold open resources to provide the user the option to release those resources.
  *
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public interface CloseableIterator<T> extends Iterator<T>, Closeable {
+public interface CloseableIterator<T> extends Iterator<T>, AutoCloseable {
 
     /**
      * Wraps an existing {@code Iterator} in a {@code CloseableIterator}. If the {@code Iterator} is already of that
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
index 0bf6160b4a..a5e3765094 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
@@ -339,8 +339,11 @@ public final class ElementHelper {
     public static <V> Optional<VertexProperty<V>> stageVertexProperty(final Vertex vertex,
                                                                       final VertexProperty.Cardinality cardinality,
                                                                       final String key, final V value, final Object... keyValues) {
-        if (cardinality.equals(VertexProperty.Cardinality.single))
-            vertex.properties(key).forEachRemaining(VertexProperty::remove);
+        if (cardinality.equals(VertexProperty.Cardinality.single)) {
+            final Iterator<VertexProperty<Object>> properties = vertex.properties(key);
+            if (null != properties)
+                properties.forEachRemaining(VertexProperty::remove);
+        }
         else if (cardinality.equals(VertexProperty.Cardinality.set)) {
             final Iterator<VertexProperty<V>> itty = vertex.properties(key);
             while (itty.hasNext()) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
similarity index 64%
rename from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java
rename to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
index 9e25631835..1242d16af7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
@@ -16,7 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.util.tools;
+
+package org.apache.tinkerpop.gremlin.util;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -24,8 +25,13 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
-public class CollectionFactory {
+public final class CollectionUtil {
+    private CollectionUtil() {
+    }
 
     public static <E> List<E> asList(final E... elements) {
         return new ArrayList<>(Arrays.asList(elements));
@@ -49,4 +55,23 @@ public class CollectionFactory {
         return map;
     }
 
+    public static <K,V> ConcurrentHashMap<K,V> clone(final ConcurrentHashMap<K,V> map) {
+        final ConcurrentHashMap<K, V> result = new ConcurrentHashMap<>(map.size());
+
+        for (Map.Entry<K, V> entry : map.entrySet()) {
+            V clonedValue;
+            if (entry.getValue() instanceof Set) {
+                clonedValue = (V) ConcurrentHashMap.newKeySet();
+                ((Set) clonedValue).addAll((Set) entry.getValue());
+            } else if (entry.getValue() instanceof ArrayList) {
+                clonedValue = (V)((ArrayList) entry.getValue()).clone();
+            } else {
+                clonedValue = entry.getValue();
+            }
+
+            result.put(entry.getKey(), clonedValue);
+        }
+
+        return result;
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/MultiMap.java
similarity index 97%
rename from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java
rename to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/MultiMap.java
index 0a5bf7b5a1..4cc2d17d58 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/MultiMap.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.util.tools;
+package org.apache.tinkerpop.gremlin.util;
 
 import java.util.Collection;
 import java.util.Collections;
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
index f7a15e35ee..a98d6a9f23 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
 import org.javatuples.Pair;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,10 +31,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asList;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asSet;
 import static org.junit.Assert.assertEquals;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asList;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asSet;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
index 99275d6030..9d17501923 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
@@ -36,10 +36,10 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
 
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asSet;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asList;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 import static org.apache.tinkerpop.gremlin.util.GremlinValueComparator.Type;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asList;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asSet;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSourceTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSourceTest.java
index 496ddd4e06..3094cbc261 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSourceTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSourceTest.java
@@ -27,7 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.Read
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -96,12 +96,12 @@ public class GraphTraversalSourceTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailMergeEForBadInput() {
-        g.mergeE(CollectionFactory.asMap(T.value, "nope"));
+        g.mergeE(CollectionUtil.asMap(T.value, "nope"));
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailMergeVForBadInput() {
-        g.mergeV(CollectionFactory.asMap(T.value, "nope"));
+        g.mergeV(CollectionUtil.asMap(T.value, "nope"));
     }
 
     /**
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalTest.java
index 84997e613b..6b5cf384b7 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalTest.java
@@ -25,7 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -76,12 +76,12 @@ public class GraphTraversalTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailMergeEWithBadInput() {
-        g.inject(0).mergeE(CollectionFactory.asMap(T.value, 100));
+        g.inject(0).mergeE(CollectionUtil.asMap(T.value, 100));
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailMergeVWithBadInput() {
-        g.inject(0).mergeV(CollectionFactory.asMap(T.value, 100));
+        g.inject(0).mergeV(CollectionUtil.asMap(T.value, 100));
     }
 
     @Test
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
index 76d95b1b06..1eb775a14e 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
@@ -26,7 +26,7 @@ import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
 import org.apache.tinkerpop.gremlin.util.function.TraverserSetSupplier;
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.junit.Test;
 
 import java.util.Collections;
@@ -41,7 +41,7 @@ public class MergeEdgeStepTest {
 
     @Test
     public void shouldValidateWithTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.OUT, new ReferenceVertex(100),
                 T.label, "knows",
@@ -51,7 +51,7 @@ public class MergeEdgeStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfBOTH() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.BOTH, new ReferenceVertex(100),
                 T.label, "knows",
@@ -61,7 +61,7 @@ public class MergeEdgeStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.OUT, new ReferenceVertex(100),
                 T.value, "knows",
@@ -71,7 +71,7 @@ public class MergeEdgeStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfBadLabel() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.OUT, new ReferenceVertex(100),
                 T.label, 100000,
@@ -81,7 +81,7 @@ public class MergeEdgeStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfWeirdKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.OUT, new ReferenceVertex(100),
                 new ReferenceVertex("weird"), 100000,
@@ -91,13 +91,13 @@ public class MergeEdgeStepTest {
 
     @Test
     public void shouldValidateWithoutTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v");
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v");
         MergeEdgeStep.validateMapInput(m, true);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithoutTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.BOTH, new ReferenceVertex(100),
                 T.label, "knows",
@@ -107,7 +107,7 @@ public class MergeEdgeStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithoutTokensBecauseOfWeirdKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 new ReferenceVertex("weird"), 100000);
         MergeEdgeStep.validateMapInput(m, true);
     }
@@ -119,15 +119,15 @@ public class MergeEdgeStepTest {
         final Traverser.Admin traverser = mock(Traverser.Admin.class);
         when(traverser.split()).thenReturn(mock(Traverser.Admin.class));
         final Traversal.Admin onCreateTraversal = mock(Traversal.Admin.class);
-        when(onCreateTraversal.next()).thenReturn(Collections.unmodifiableMap(CollectionFactory.asMap("key1", "value1")));
+        when(onCreateTraversal.next()).thenReturn(Collections.unmodifiableMap(CollectionUtil.asMap("key1", "value1")));
         when(onCreateTraversal.getSideEffects()).thenReturn(mock(TraversalSideEffects.class));
 
         final MergeEdgeStep step = new MergeEdgeStep<>(traversal, true);
         step.addChildOption(Merge.onCreate, onCreateTraversal);
 
-        final Map mergeMap = CollectionFactory.asMap("key2", "value2");
+        final Map mergeMap = CollectionUtil.asMap("key2", "value2");
         final Map onCreateMap = step.onCreateMap(traverser, new LinkedHashMap<>(), mergeMap);
 
-        assertEquals(CollectionFactory.asMap("key1", "value1", "key2", "value2"), onCreateMap);
+        assertEquals(CollectionUtil.asMap("key1", "value1", "key2", "value2"), onCreateMap);
     }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
index f5d640613b..81a35bbd03 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
@@ -26,7 +26,7 @@ import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
 import org.apache.tinkerpop.gremlin.util.function.TraverserSetSupplier;
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.junit.Test;
 
 import java.util.Collections;
@@ -40,7 +40,7 @@ public class MergeVertexStepTest {
 
     @Test
     public void shouldValidateWithTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.label, "person",
                 T.id, 10000);
         MergeVertexStep.validateMapInput(m, false);
@@ -48,7 +48,7 @@ public class MergeVertexStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.value, "nope",
                 T.label, "person",
                 T.id, 10000);
@@ -57,7 +57,7 @@ public class MergeVertexStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithTokensBecauseOfWeirdKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, "weird",
                 T.id, 10000);
         MergeVertexStep.validateMapInput(m, false);
@@ -65,13 +65,13 @@ public class MergeVertexStepTest {
 
     @Test
     public void shouldValidateWithoutTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v");
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v");
         MergeVertexStep.validateMapInput(m, true);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithoutTokens() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, 101,
                 Direction.BOTH, new ReferenceVertex(100),
                 T.label, "knows",
@@ -81,7 +81,7 @@ public class MergeVertexStepTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithoutTokensBecauseOfWeirdKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 new ReferenceVertex("weird"), 100000);
         MergeVertexStep.validateMapInput(m, true);
     }
@@ -93,70 +93,70 @@ public class MergeVertexStepTest {
         final Traverser.Admin traverser = mock(Traverser.Admin.class);
         when(traverser.split()).thenReturn(mock(Traverser.Admin.class));
         final Traversal.Admin onCreateTraversal = mock(Traversal.Admin.class);
-        when(onCreateTraversal.next()).thenReturn(Collections.unmodifiableMap(CollectionFactory.asMap("key1", "value1")));
+        when(onCreateTraversal.next()).thenReturn(Collections.unmodifiableMap(CollectionUtil.asMap("key1", "value1")));
         when(onCreateTraversal.getSideEffects()).thenReturn(mock(TraversalSideEffects.class));
 
         final MergeVertexStep step = new MergeVertexStep(traversal, true);
         step.addChildOption(Merge.onCreate, onCreateTraversal);
 
-        final Map mergeMap = CollectionFactory.asMap("key2", "value2");
+        final Map mergeMap = CollectionUtil.asMap("key2", "value2");
         final Map onCreateMap = step.onCreateMap(traverser, mergeMap);
 
-        assertEquals(CollectionFactory.asMap("key1", "value1", "key2", "value2"), onCreateMap);
+        assertEquals(CollectionUtil.asMap("key1", "value1", "key2", "value2"), onCreateMap);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithNullKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 null, "person");
         MergeVertexStep.validateMapInput(m, true);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithNullLabelValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.label, null);
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithObjectAsLabelValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.label, new Object());
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithNullIdValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.id, null);
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithNullDirectionValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 Direction.IN, null);
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithHiddenIdKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 "~id", 10000);
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithHiddenLabelKey() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 "~label", "person");
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void shouldFailToValidateWithHiddenLabelValue() {
-        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
                 T.label, "~person");
         MergeVertexStep.validateMapInput(m, false);
     }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
new file mode 100644
index 0000000000..73bd39b415
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import org.apache.tinkerpop.gremlin.AssertHelper;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.Assert.assertTrue;
+
+public class CollectionUtilTest {
+
+    @Test
+    public void shouldBeUtilityClass() throws Exception {
+        AssertHelper.assertIsUtilityClass(CollectionUtil.class);
+    }
+
+    @Test
+    public void shouldCloneConcurrentHashMapWithPrimitiveValues() {
+        final ConcurrentHashMap<Object, String> source = new ConcurrentHashMap<>();
+        source.put("key", "value");
+        source.put(1, "test");
+
+        final ConcurrentHashMap<?,?> cloned = CollectionUtil.clone(source);
+        assertTrue(source.equals(cloned));
+    }
+
+    @Test
+    public void shouldCloneConcurrentHashMapWithArrayListValues() {
+        final ConcurrentHashMap<String, ArrayList<String>> source = new ConcurrentHashMap<>();
+        final ArrayList<String> value = new ArrayList<>();
+        value.add("test1");
+        value.add("test2");
+        source.put("key", value);
+        source.put("empty", new ArrayList<>());
+
+        final ConcurrentHashMap<?, ?> cloned = CollectionUtil.clone(source);
+        assertTrue(source.equals(cloned));
+    }
+
+    @Test
+    public void shouldCloneConcurrentHashMapWithSetValues() {
+        final ConcurrentHashMap<String, Set<String>> source = new ConcurrentHashMap<>();
+        final HashSet<String> value = new HashSet<>();
+        value.add("test1");
+        value.add("test2");
+        source.put("key", value);
+        source.put("empty", new HashSet<>());
+
+        final ConcurrentHashMap<?, ?> cloned = CollectionUtil.clone(source);
+        assertTrue(source.equals(cloned));
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/tools/MultiMapTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/MultiMapTest.java
similarity index 98%
rename from gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/tools/MultiMapTest.java
rename to gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/MultiMapTest.java
index c502a5165c..2a4cef4a87 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/tools/MultiMapTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/MultiMapTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.util.tools;
+package org.apache.tinkerpop.gremlin.util;
 
 import org.apache.tinkerpop.gremlin.AssertHelper;
 import org.junit.Test;
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
index 257f20b530..0fc0bc1beb 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
@@ -23,75 +23,80 @@
 
 using System;
 using System.Threading.Tasks;
-using Gremlin.Net.Process.Remote;
 using Gremlin.Net.Process.Traversal;
 using Xunit;
 
 namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
 {
-    public class GraphTraversalTransactionTests : IDisposable
+    public class GraphTraversalTransactionTests
     {
-        private readonly IRemoteConnection _connection = new RemoteConnectionFactory().CreateRemoteConnection("gtx");
+        public static object[][] Graphs => new[] 
+        {
+            new [] { "gtx" },
+        };
 
         [IgnoreIfTransactionsNotSupportedFact]
-        public async Task ShouldSupportRemoteTransactionsCommit()
+        [MemberData(nameof(Graphs))]        
+        public async Task ShouldSupportRemoteTransactionsCommit(string graph)
         {
-            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+            var g = GetGts(graph);
             var tx = g.Tx();
             var gtx = tx.Begin();
             await gtx.AddV("person").Property("name", "jorge").Promise(t => t.Iterate()).ConfigureAwait(false);
             await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
-            
+
             // Assert within the transaction
             var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
             Assert.Equal(2, count);
-            
+
             // Vertices should not be visible in a different transaction before commiting
             count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
             Assert.Equal(0, count);
-            
+
             // Now commit changes to test outside of the transaction
             await tx.CommitAsync().ConfigureAwait(false);
 
             count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
             Assert.Equal(2, count);
+
+            EmptyGraph(g);
         }
-        
+
         [IgnoreIfTransactionsNotSupportedFact]
-        public async Task ShouldSupportRemoteTransactionsRollback()
+        [MemberData(nameof(Graphs))]
+        public async Task ShouldSupportRemoteTransactionsRollback(string graph)
         {
-            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+            var g = GetGts(graph);
             var tx = g.Tx();
             var gtx = tx.Begin();
             await gtx.AddV("person").Property("name", "jorge").Promise(t => t.Iterate()).ConfigureAwait(false);
             await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
-            
+
             // Assert within the transaction
             var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
             Assert.Equal(2, count);
-            
+
             // Now rollback changes to test outside of the transaction
             await tx.RollbackAsync().ConfigureAwait(false);
 
             count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
             Assert.Equal(0, count);
-            
-            g.V().Count().Next();
+
+            EmptyGraph(g);
         }
 
-        public void Dispose()
+        private GraphTraversalSource GetGts(string graph)
         {
-            EmptyGraph();
+            return AnonymousTraversalSource.Traversal().WithRemote(new RemoteConnectionFactory().CreateRemoteConnection(graph));
         }
 
-        private void EmptyGraph()
+        private static void EmptyGraph(GraphTraversalSource g)
         {
-            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
             g.V().Drop().Iterate();
         }
     }
 
-    public sealed class IgnoreIfTransactionsNotSupportedFact : FactAttribute
+    public sealed class IgnoreIfTransactionsNotSupportedFact : TheoryAttribute
     {
         public IgnoreIfTransactionsNotSupportedFact()
         {
diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go
index 73855c31d5..c8c217037d 100644
--- a/gremlin-go/driver/cucumber/cucumberSteps_test.go
+++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go
@@ -24,8 +24,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/apache/tinkerpop/gremlin-go/v3/driver"
-	"github.com/cucumber/godog"
 	"math"
 	"reflect"
 	"regexp"
@@ -33,6 +31,9 @@ import (
 	"strings"
 	"sync"
 	"testing"
+
+	gremlingo "github.com/apache/tinkerpop/gremlin-go/v3/driver"
+	"github.com/cucumber/godog"
 )
 
 type tinkerPopGraph struct {
@@ -640,6 +641,9 @@ func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{})
 	if fmt.Sprint(expected) == fmt.Sprint(actual) {
 		return true
 	}
+	if len(expected) != len(actual) {
+		return false
+	}
 	expectedCopy := make([]interface{}, len(expected))
 	copy(expectedCopy, expected)
 	for _, a := range actual {
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
index a9a2c565ee..3f761432e8 100644
--- a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
@@ -20,13 +20,12 @@
 package org.apache.tinkerpop.gremlin.groovy.jsr223.ast
 
 import org.apache.tinkerpop.gremlin.process.traversal.Order
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
 import org.apache.tinkerpop.gremlin.process.traversal.Translator
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode
 import org.apache.tinkerpop.gremlin.process.traversal.Bindings
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory
+import org.apache.tinkerpop.gremlin.util.CollectionUtil
 import org.codehaus.groovy.ast.ASTNode
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.CodeVisitorSupport
@@ -123,7 +122,7 @@ class VarAsBindingASTTransformation implements ASTTransformation {
                                 case GraphTraversal.Symbols.mergeE:
                                 case GraphTraversal.Symbols.mergeV:
                                     bindingValue = new MethodCallExpression(
-                                        new ClassExpression(new ClassNode(CollectionFactory)), "asMap",
+                                        new ClassExpression(new ClassNode(CollectionUtil)), "asMap",
                                         new TupleExpression(new ConstantExpression(UUID.randomUUID().toString()),
                                                             new ConstantExpression(UUID.randomUUID().toString())))
                                     break
@@ -131,7 +130,7 @@ class VarAsBindingASTTransformation implements ASTTransformation {
                                 case GraphTraversal.Symbols.option:
                                     if (i == 1)
                                         bindingValue = new MethodCallExpression(
-                                            new ClassExpression(new ClassNode(CollectionFactory)), "asMap",
+                                            new ClassExpression(new ClassNode(CollectionUtil)), "asMap",
                                             new TupleExpression(new ConstantExpression(UUID.randomUUID().toString()),
                                                                 new ConstantExpression(UUID.randomUUID().toString())))
                                     break
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
index cefd37fcc8..eafddc0a72 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
@@ -160,11 +160,11 @@ describe('Traversal', function () {
   describe("more complex traversals", function() {
     it('should return paths of value maps', function() {
       const g = traversal().withRemote(connection);
-      return g.V(1).out().in_().limit(1).path().by(__.valueMap('name')).toList().then(function (list) {
+      return g.V(1).out().order().in_().order().limit(1).path().by(__.valueMap('name')).toList().then(function (list) {
         assert.ok(list);
         assert.strictEqual(list.length, 1);
         assert.strictEqual(list[0].objects[0].get('name')[0], "marko");
-        assert.strictEqual(list[0].objects[1].get('name')[0], "lop");
+        assert.strictEqual(list[0].objects[1].get('name')[0], "vadas");
         assert.strictEqual(list[0].objects[2].get('name')[0], "marko");
       });
     });
diff --git a/docker/gremlin-server/gremlin-server-integration.yaml b/gremlin-server/conf/gremlin-server-transaction.yaml
similarity index 63%
copy from docker/gremlin-server/gremlin-server-integration.yaml
copy to gremlin-server/conf/gremlin-server-transaction.yaml
index 74b4ba4953..1b0b397c5e 100644
--- a/docker/gremlin-server/gremlin-server-integration.yaml
+++ b/gremlin-server/conf/gremlin-server-transaction.yaml
@@ -15,39 +15,30 @@
 # specific language governing permissions and limitations
 # under the License.
 
-host: 0.0.0.0
-port: 45940
+host: localhost
+port: 8182
 evaluationTimeout: 30000
-channelizer: org.apache.tinkerpop.gremlin.server.channel.WsAndHttpChannelizer
+channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer
 graphs: {
-  graph: conf/tinkergraph-service.properties,
-  immutable: conf/tinkergraph-service.properties,
-  classic: conf/tinkergraph-service.properties,
-  modern: conf/tinkergraph-service.properties,
-  crew: conf/tinkergraph-service.properties,
-  grateful: conf/tinkergraph-service.properties,
-  sink: conf/tinkergraph-service.properties
-}
+  graph: conf/tinkertransactiongraph-empty.properties}
 scriptEngines: {
-  gremlin-lang : {},
   gremlin-groovy: {
     plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
                org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000},
                org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}}
+               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}}
 serializers:
-  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}
-  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV2] }}
-  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV1] }}
-  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }
-  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}
+  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}            # application/json
+  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }                                                                                                           # application/vnd.graphbinary-v1.0
+  - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}                                                                 # application/vnd.graphbinary-v1.0-stringd
 processors:
   - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
-  - { className: org.apache.tinkerpop.gremlin.server.op.standard.StandardOpProcessor, config: {}}
+  - { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor}
 metrics: {
+  consoleReporter: {enabled: true, interval: 180000},
+  csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv},
+  jmxReporter: {enabled: true},
   slf4jReporter: {enabled: true, interval: 180000}}
-gremlinPool: 8
 strictTransactionManagement: false
 idleConnectionTimeout: 0
 keepAliveInterval: 0
@@ -59,3 +50,5 @@ maxAccumulationBufferComponents: 1024
 resultIterationBatchSize: 64
 writeBufferLowWaterMark: 32768
 writeBufferHighWaterMark: 65536
+ssl: {
+  enabled: false}
\ No newline at end of file
diff --git a/gremlin-server/conf/tinkertransactiongraph-empty.properties b/gremlin-server/conf/tinkertransactiongraph-empty.properties
new file mode 100644
index 0000000000..bb172e4bf3
--- /dev/null
+++ b/gremlin-server/conf/tinkertransactiongraph-empty.properties
@@ -0,0 +1,18 @@
+# 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.
+gremlin.graph=org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph
+gremlin.tinkergraph.vertexIdManager=LONG
\ No newline at end of file
diff --git a/gremlin-server/conf/tinkertransactiongraph-service.properties b/gremlin-server/conf/tinkertransactiongraph-service.properties
new file mode 100644
index 0000000000..86103c100d
--- /dev/null
+++ b/gremlin-server/conf/tinkertransactiongraph-service.properties
@@ -0,0 +1,22 @@
+# 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.
+gremlin.graph=org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph
+gremlin.tinkergraph.vertexIdManager=INTEGER
+gremlin.tinkergraph.edgeIdManager=INTEGER
+gremlin.tinkergraph.vertexPropertyIdManager=LONG
+gremlin.tinkergraph.service=org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerTextSearchFactory
+gremlin.tinkergraph.service=org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerDegreeCentralityFactory
diff --git a/gremlin-server/pom.xml b/gremlin-server/pom.xml
index 6cf86e2e12..5f5c581f96 100644
--- a/gremlin-server/pom.xml
+++ b/gremlin-server/pom.xml
@@ -279,128 +279,6 @@ limitations under the License.
             </build>
         </profile>
 
-        <profile>
-            <!--
-              This profile will include neo4j for purposes of transactional testing within Gremlin Server.
-              Tests that require neo4j specifically will be "ignored" if this profile is not turned on.
-            -->
-            <id>include-neo4j</id>
-            <activation>
-                <activeByDefault>false</activeByDefault>
-                <property>
-                    <name>includeNeo4j</name>
-                </property>
-            </activation>
-            <dependencies>
-                <!-- TESTING -->
-                <!-- *** WARNING *** -->
-                <dependency>
-                    <groupId>org.neo4j</groupId>
-                    <artifactId>neo4j-tinkerpop-api-impl</artifactId>
-                    <version>0.9-3.4.0</version>
-                    <scope>test</scope>
-                    <exclusions>
-                        <exclusion>
-                            <groupId>org.neo4j</groupId>
-                            <artifactId>neo4j-kernel</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.apache.commons</groupId>
-                            <artifactId>commons-lang3</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.apache.commons</groupId>
-                            <artifactId>commons-text</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>com.github.ben-manes.caffeine</groupId>
-                            <artifactId>caffeine</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.scala-lang</groupId>
-                            <artifactId>scala-library</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.scala-lang</groupId>
-                            <artifactId>scala-reflect</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.slf4j</groupId>
-                            <artifactId>slf4j-api</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.slf4j</groupId>
-                            <artifactId>slf4j-nop</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.apache.lucene</groupId>
-                            <artifactId>lucene-core</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>io.dropwizard.metrics</groupId>
-                            <artifactId>metrics-core</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>io.netty</groupId>
-                            <artifactId>netty-all</artifactId>
-                        </exclusion>
-                        <exclusion>
-                            <groupId>org.ow2.asm</groupId>
-                            <artifactId>asm</artifactId>
-                        </exclusion>
-                    </exclusions>
-                </dependency>
-                <dependency>
-                    <groupId>org.scala-lang</groupId>
-                    <artifactId>scala-library</artifactId>
-                    <version>2.11.8</version>
-                    <scope>test</scope>
-                </dependency>
-                <dependency>
-                    <groupId>org.scala-lang</groupId>
-                    <artifactId>scala-reflect</artifactId>
-                    <version>2.11.8</version>
-                    <scope>test</scope>
-                </dependency>
-                <dependency>
-                    <groupId>org.apache.lucene</groupId>
-                    <artifactId>lucene-core</artifactId>
-                    <version>5.5.0</version>
-                    <scope>test</scope>
-                </dependency>
-                <dependency>
-                    <groupId>io.dropwizard.metrics</groupId>
-                    <artifactId>metrics-core</artifactId>
-                    <version>4.2.11</version>
-                    <scope>test</scope>
-                </dependency>
-                <dependency>
-                    <groupId>org.neo4j</groupId>
-                    <artifactId>neo4j-kernel</artifactId>
-                    <version>3.4.11</version>
-                    <scope>test</scope>
-                    <exclusions>
-                        <exclusion>
-                            <groupId>io.netty</groupId>
-                            <artifactId>netty-all</artifactId>
-                        </exclusion>
-                    </exclusions>
-                </dependency>
-                <!-- *** WARNING *** -->
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-deploy-plugin</artifactId>
-                        <configuration>
-                            <skip>true</skip>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
         <profile>
             <id>docker-images</id>
             <activation>
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/AbstractGremlinServerIntegrationTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/AbstractGremlinServerIntegrationTest.java
index 6a8acbad15..147014c138 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/AbstractGremlinServerIntegrationTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/AbstractGremlinServerIntegrationTest.java
@@ -22,7 +22,7 @@ import org.apache.tinkerpop.gremlin.server.channel.UnifiedChannelizer;
 import org.apache.tinkerpop.gremlin.server.channel.UnifiedChannelizerIntegrateTest;
 import org.apache.tinkerpop.gremlin.server.op.OpLoader;
 import org.apache.tinkerpop.gremlin.server.util.ServerGremlinExecutor;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -150,7 +150,7 @@ public abstract class AbstractGremlinServerIntegrationTest {
         // calling close() on TinkerGraph does not free resources quickly enough. adding a clear() call let's gc
         // cleanup earlier
         server.getServerGremlinExecutor().getGraphManager().getAsBindings().values().stream()
-                .filter(g -> g instanceof TinkerGraph).forEach(g -> ((TinkerGraph) g).clear());
+                .filter(g -> g instanceof AbstractTinkerGraph).forEach(g -> ((AbstractTinkerGraph) g).clear());
 
         if (server != null) {
             server.stop().join();
@@ -183,25 +183,9 @@ public abstract class AbstractGremlinServerIntegrationTest {
         return (directory.delete());
     }
 
-    protected static void tryIncludeNeo4jGraph(final Settings settings) {
-        if (isNeo4jPresent()) {
-            deleteDirectory(new File("/tmp/neo4j"));
-            settings.graphs.put("graph", "conf/neo4j-empty.properties");
-        }
-    }
-
-    protected static boolean isNeo4jPresent() {
-        try {
-            Class.forName("org.neo4j.tinkerpop.api.impl.Neo4jGraphAPIImpl");
-            return true;
-        } catch (Throwable ex) {
-            return false;
-        }
-    }
-
-    protected static void assumeNeo4jIsPresent() {
-        boolean neo4jIncludedForTesting = isNeo4jPresent();
-        assumeThat("Neo4j implementation was not included for testing - run with -DincludeNeo4j", neo4jIncludedForTesting, is(true));
+    protected static void useTinkerTransactionGraph(final Settings settings) {
+        logger.info("Running transactional tests using TinkerTransactionGraph");
+        settings.graphs.put("graph", "conf/tinkertransactiongraph-empty.properties");
     }
 
     private boolean shouldTestUnified() {
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 8c179db2e8..891d676c55 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
@@ -182,14 +182,14 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
             case "shouldExecuteScriptInSessionOnTransactionalWithManualTransactionsGraph":
             case "shouldExecuteInSessionAndSessionlessWithoutOpeningTransaction":
             case "shouldManageTransactionsInSession":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 break;
             case "shouldRequireAliasedGraphVariablesInStrictTransactionMode":
                 settings.strictTransactionManagement = true;
                 break;
             case "shouldAliasGraphVariablesInStrictTransactionMode":
                 settings.strictTransactionManagement = true;
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 break;
             case "shouldProcessSessionRequestsInOrderAfterTimeout":
                 settings.evaluationTimeout = 250;
@@ -1139,7 +1139,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldExecuteScriptInSessionOnTransactionalGraph() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client client = cluster.connect(name.getMethodName());
@@ -1162,7 +1161,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldExecuteScriptInSessionOnTransactionalWithManualTransactionsGraph() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client client = cluster.connect(name.getMethodName());
@@ -1199,7 +1197,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldExecuteInSessionAndSessionlessWithoutOpeningTransaction() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         try {
@@ -1229,7 +1226,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldExecuteSessionlessScriptOnTransactionalGraph() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client client = cluster.connect();
@@ -1401,7 +1397,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldAliasGraphVariablesInStrictTransactionMode() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client client = cluster.connect();
@@ -1519,7 +1514,6 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration
 
     @Test
     public void shouldManageTransactionsInSession() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client client = cluster.connect();
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
index dcacd602eb..a175ebd277 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
@@ -89,10 +89,10 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
                 settings.maxContentLength = 31;
                 break;
             case "should200OnPOSTTransactionalGraph":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 break;
             case "should200OnPOSTTransactionalGraphInStrictMode":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 settings.strictTransactionManagement = true;
                 break;
             case "should200OnPOSTWithGraphSON1d0AcceptHeaderDefaultResultToJson":
@@ -559,7 +559,6 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
 
     @Test
     public void should200OnPOSTTransactionalGraph() throws Exception {
-        assumeNeo4jIsPresent();
 
         final CloseableHttpClient httpclient = HttpClients.createDefault();
         final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
@@ -592,7 +591,6 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
 
     @Test
     public void should200OnPOSTTransactionalGraphInStrictMode() throws Exception {
-        assumeNeo4jIsPresent();
 
         final CloseableHttpClient httpclient = HttpClients.createDefault();
         final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index b741b02507..51241a5474 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -252,7 +252,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
                 break;
             case "shouldRespondToTimeoutCancelledWsRequest":
             case "shouldRespondToTimeoutCancelledSessionRequest":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 settings.evaluationTimeout = 5000;
                 settings.gremlinPool = POOL_SIZE_FOR_TIMEOUT_TESTS;
                 settings.channelizer = WebSocketChannelizer.class.getName();
@@ -263,7 +263,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
                 settings.channelizer = UnifiedChannelizer.class.getName();
                 break;
             case "shouldRespondToTimeoutCancelledMultiTaskUnifiedRequest":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 settings.evaluationTimeout = 30000;
                 settings.sessionLifetimeTimeout = 5000; // This needs to be shorter because of the delay in scheduling session task.
                 settings.gremlinPool = POOL_SIZE_FOR_TIMEOUT_TESTS;
@@ -1192,7 +1192,6 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
         // Don't test with UnifiedChannelizer since we only want to test the case where a task is cancelled before
         // running which is handled by shouldRespondToTimeoutCancelledMultiTaskUnifiedRequest.
         assumeThat("Must use OpProcessor", isUsingUnifiedChannelizer(), is(false));
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -1219,7 +1218,6 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
      */
     @Test(timeout = 180000) // Add timeout in case the test hangs.
     public void shouldRespondToTimeoutCancelledMultiTaskUnifiedRequest() throws Exception {
-        assumeNeo4jIsPresent();
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
         final GraphTraversalSource gtx = g.tx().begin();
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSessionIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSessionIntegrateTest.java
index 61ab6f8399..2b6c25f5bb 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSessionIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSessionIntegrateTest.java
@@ -131,7 +131,7 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
             case "shouldExecuteInSessionWithTransactionManagement":
             case "shouldRollbackOnEvalExceptionForManagedTransaction":
             case "shouldNotExecuteQueuedRequestsIfOneInFrontOfItFails":
-                tryIncludeNeo4jGraph(settings);
+                useTinkerTransactionGraph(settings);
                 break;
             case "shouldEnsureSessionBindingsAreThreadSafe":
                 settings.threadPoolWorker = 2;
@@ -165,7 +165,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
 
     @Test
     public void shouldBlowTheSessionQueueSize() throws Exception {
-        assumeNeo4jIsPresent();
         assumeThat(isUsingUnifiedChannelizer(), is(true));
 
         final Cluster cluster = TestClientFactory.open();
@@ -207,7 +206,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
 
     @Test
     public void shouldNotExecuteQueuedRequestsIfOneInFrontOfItFails() throws Exception {
-        assumeNeo4jIsPresent();
         assumeThat(isUsingUnifiedChannelizer(), is(true));
 
         final Cluster cluster = TestClientFactory.open();
@@ -259,7 +257,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
 
     @Test
     public void shouldCloseSessionOnClientClose() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster1 = TestClientFactory.open();
         final Client client1 = cluster1.connect(name.getMethodName());
@@ -307,7 +304,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
 
     @Test
     public void shouldCloseSessionOnClientCloseWithStateMaintainedBetweenExceptions() throws Exception {
-        assumeNeo4jIsPresent();
         assumeThat("Must use UnifiedChannelizer", isUsingUnifiedChannelizer(), is(true));
 
         final Cluster cluster1 = TestClientFactory.open();
@@ -416,7 +412,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
 
     @Test
     public void shouldRollbackOnEvalExceptionForManagedTransaction() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.open();
         final Client.SessionSettings sessionSettings = Client.SessionSettings.build().
@@ -576,7 +571,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
     @Test
     @SuppressWarnings("unchecked")
     public void shouldExecuteInSessionAndSessionlessWithoutOpeningTransactionWithSingleClient() throws Exception {
-        assumeNeo4jIsPresent();
 
         try (final SimpleClient client = TestClientFactory.createWebSocketClient()) {
 
@@ -643,7 +637,6 @@ public class GremlinServerSessionIntegrateTest extends AbstractGremlinServerInte
     @Test
     @SuppressWarnings("unchecked")
     public void shouldExecuteInSessionWithTransactionManagement() throws Exception {
-        assumeNeo4jIsPresent();
 
         try (final SimpleClient client = TestClientFactory.createWebSocketClient()) {
             final RequestMessage addRequest = RequestMessage.build(Tokens.OPS_EVAL)
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinSessionTxIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinSessionTxIntegrateTest.java
index 08b50db1b1..cf22cf9156 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinSessionTxIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinSessionTxIntegrateTest.java
@@ -56,8 +56,7 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
     public Settings overrideSettings(final Settings settings) {
         final String nameOfTest = name.getMethodName();
 
-        deleteDirectory(new File("/tmp/neo4j"));
-        settings.graphs.put("graph", "conf/neo4j-empty.properties");
+        settings.graphs.put("graph", "conf/tinkertransactiongraph-empty.properties");
 
         switch (nameOfTest) {
             case "shouldExecuteBytecodeInSession":
@@ -83,7 +82,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
     @Test
     @Ignore("TINKERPOP-2832")
     public void shouldTimeoutTxBytecode() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -116,7 +114,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitTxBytecodeInSession() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -150,7 +147,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitTxBytecodeInSessionWithExplicitTransactionObject() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -171,7 +167,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldRollbackTxBytecodeInSession() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -201,7 +196,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitTxBytecodeInSessionOnCloseOfGtx() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -231,7 +225,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitTxBytecodeInSessionOnCloseTx() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -261,7 +254,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldOpenAndCloseObsceneAmountOfSessions() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -294,7 +286,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitTxBytecodeInSessionReusingGtxAcrossThreads() throws Exception {
-        assumeNeo4jIsPresent();
 
         final ExecutorService service = Executors.newFixedThreadPool(2);
 
@@ -327,7 +318,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldSpawnMultipleTraversalSourceInSameTransaction() throws Exception {
-        assumeNeo4jIsPresent();
 
         final Cluster cluster = TestClientFactory.build().create();
         final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
@@ -376,8 +366,6 @@ public class GremlinSessionTxIntegrateTest extends AbstractGremlinServerIntegrat
 
     @Test
     public void shouldCommitRollbackInScriptUsingGremlinLang() throws Exception {
-        assumeNeo4jIsPresent();
-
         final Cluster cluster = TestClientFactory.open();
         final Client.SessionSettings sessionSettings = Client.SessionSettings.build().
                 sessionId(name.getMethodName()).
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java
index 53900fc822..5cd49c8b04 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java
@@ -66,7 +66,7 @@ public class CheckedGraphManagerTest {
     public void justAGraphFails() {
         settings.graphs.put("invalid", "conf/invalidPath");
         final GraphManager manager = new CheckedGraphManager(settings);
-        assertThat(manager.getGraphNames(), hasSize(6));
+        assertThat(manager.getGraphNames(), hasSize(7));
     }
 
     /**
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java
index 6c0e8b0567..daf04bc158 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java
@@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.server.GraphManager;
 import org.apache.tinkerpop.gremlin.server.Settings;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph;
 import org.junit.Test;
 
 import javax.script.Bindings;
@@ -47,7 +48,7 @@ public class DefaultGraphManagerTest {
         final Set<String> graphNames = graphManager.getGraphNames();
 
         assertNotNull(graphNames);
-        assertEquals(6, graphNames.size());
+        assertEquals(7, graphNames.size());
 
         assertThat(graphNames.contains("graph"), is(true));
         assertThat(graphNames.contains("classic"), is(true));
@@ -55,7 +56,9 @@ public class DefaultGraphManagerTest {
         assertThat(graphNames.contains("crew"), is(true));
         assertThat(graphNames.contains("sink"), is(true));
         assertThat(graphNames.contains("grateful"), is(true));
+        assertThat(graphNames.contains("tx"), is(true));
         assertThat(graphManager.getGraph("graph"), instanceOf(TinkerGraph.class));
+        assertThat(graphManager.getGraph("tx"), instanceOf(TinkerTransactionGraph.class));
     }
 
     @Test
@@ -65,14 +68,16 @@ public class DefaultGraphManagerTest {
         final Bindings bindings = graphManager.getAsBindings();
 
         assertNotNull(bindings);
-        assertEquals(6, bindings.size());
+        assertEquals(7, bindings.size());
         assertThat(bindings.containsKey("graph"), is(true));
         assertThat(bindings.containsKey("classic"), is(true));
         assertThat(bindings.containsKey("modern"), is(true));
         assertThat(bindings.containsKey("crew"), is(true));
         assertThat(bindings.containsKey("sink"), is(true));
         assertThat(bindings.containsKey("grateful"), is(true));
+        assertThat(bindings.containsKey("tx"), is(true));
         assertThat(bindings.get("graph"), instanceOf(TinkerGraph.class));
+        assertThat(bindings.get("tx"), instanceOf(TinkerTransactionGraph.class));
     }
 
     @Test
@@ -94,7 +99,7 @@ public class DefaultGraphManagerTest {
 
         final Set<String> graphNames = graphManager.getGraphNames();
         assertNotNull(graphNames);
-        assertEquals(7, graphNames.size());
+        assertEquals(8, graphNames.size());
         assertThat(graphNames.contains("newGraph"), is(true));
         assertThat(graphNames.contains("graph"), is(true));
         assertThat(graphNames.contains("classic"), is(true));
@@ -102,7 +107,9 @@ public class DefaultGraphManagerTest {
         assertThat(graphNames.contains("crew"), is(true));
         assertThat(graphNames.contains("sink"), is(true));
         assertThat(graphNames.contains("grateful"), is(true));
+        assertThat(graphNames.contains("tx"), is(true));
         assertThat(graphManager.getGraph("newGraph"), instanceOf(TinkerGraph.class));
+        assertThat(graphManager.getGraph("tx"), instanceOf(TinkerTransactionGraph.class));
     }
 
     @Test
@@ -113,14 +120,14 @@ public class DefaultGraphManagerTest {
         graphManager.putGraph("newGraph", graph);
         final Set<String> graphNames = graphManager.getGraphNames();
         assertNotNull(graphNames);
-        assertEquals(7, graphNames.size());
+        assertEquals(8, graphNames.size());
         assertThat(graphNames.contains("newGraph"), is(true));
         assertThat(graphManager.getGraph("newGraph"), instanceOf(TinkerGraph.class));
 
         graphManager.removeGraph("newGraph");
 
         final Set<String> graphNames2 = graphManager.getGraphNames();
-        assertEquals(6, graphNames2.size());
+        assertEquals(7, graphNames2.size());
         assertThat(graphNames2.contains("newGraph"), is(false));
     }
 
diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
index a6404a70a6..15904893cd 100644
--- a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
+++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
@@ -38,7 +38,9 @@ graphs: {
   modern: conf/tinkergraph-empty.properties,
   crew: conf/tinkergraph-empty.properties,
   grateful: conf/tinkergraph-empty.properties,
-  sink: conf/tinkergraph-empty.properties}
+  sink: conf/tinkergraph-empty.properties,
+  tx: conf/tinkertransactiongraph-empty.properties
+}
 scriptEngines: {
   gremlin-lang : {},
   gremlin-groovy: {
diff --git a/gremlin-server/src/test/scripts/generate-all.groovy b/gremlin-server/src/test/scripts/generate-all.groovy
index 88ca00ee5a..9578ccb027 100644
--- a/gremlin-server/src/test/scripts/generate-all.groovy
+++ b/gremlin-server/src/test/scripts/generate-all.groovy
@@ -36,7 +36,7 @@ globals << [hook : [
     // things now, that test config would have been mixed in with release artifacts and there would have been ugly
     // exclusions to make packaging work properly.
     allowSetOfIdManager = { graph, idManagerFieldName, idManager ->
-        java.lang.reflect.Field idManagerField = graph.class.getDeclaredField(idManagerFieldName)
+        java.lang.reflect.Field idManagerField = graph.class.getSuperclass().getDeclaredField(idManagerFieldName)
         idManagerField.setAccessible(true)
         idManagerField.set(graph, idManager)
     }
@@ -51,7 +51,6 @@ globals << [hook : [
     TinkerFactory.generateTheCrew(crew)
     TinkerFactory.generateGratefulDead(grateful)
     TinkerFactory.generateKitchenSink(sink)
-
   }
 ] as LifeCycleHook]
 
@@ -63,13 +62,7 @@ globals << [gcrew : traversal().withEmbedded(crew)]
 globals << [ggraph : traversal().withEmbedded(graph)]
 globals << [ggrateful : traversal().withEmbedded(grateful)]
 globals << [gsink : traversal().withEmbedded(sink)]
-
-// dynamically detect existence of gtx as it may or may not be present depending on the -DincludeNeo4j
-// and the configuration of the particular server instance. with docker/gremlin-server.sh the neo4j
-// "tx" configuration is already present and will therefore be enabled.
-def dynamicGtx = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE)["tx"]
-if (dynamicGtx != null)
-    globals << [gtx : traversal().withEmbedded(dynamicGtx)]
+globals << [gtx : traversal().withEmbedded(tx)]
 
 // dynamically detect existence of gimmutable as it is only used in gremlin-go testing suite
 def dynamicGimmutable = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE)["immutable"]
diff --git a/gremlin-server/src/test/scripts/test-server-start.groovy b/gremlin-server/src/test/scripts/test-server-start.groovy
index 7119d2b1b2..d5eaab0c38 100644
--- a/gremlin-server/src/test/scripts/test-server-start.groovy
+++ b/gremlin-server/src/test/scripts/test-server-start.groovy
@@ -34,19 +34,8 @@ import org.apache.tinkerpop.gremlin.server.Settings
 ////////////////////////////////////////////////////////////////////////////////
 
 if (Boolean.parseBoolean(skipTests)) return
-def testTransactions = System.getProperty("includeNeo4j") != null
-
-if (testTransactions) {
-    // clean up prior neo4j instances
-    def tempNeo4jDir = new java.io.File("/tmp/neo4j")
-    if (tempNeo4jDir.exists()) {
-        log.info("Cleaning up prior Neo4j test instances for ${executionName}")
-        org.apache.commons.io.FileUtils.deleteDirectory(tempNeo4jDir)
-    }
-}
 
 log.info("Starting Gremlin Server instances for native testing of ${executionName}")
-log.info("Transactions validated (enabled with -DincludeNeo4j and only available on port 45940): " + testTransactions)
 
 def platformAgnosticBaseDirPath = "${tinkerpopRootDir}".replace("\\", "/")
 def platformAgnosticGremlinServerDir = platformAgnosticBaseDirPath + "/gremlin-server"
@@ -60,7 +49,7 @@ settings.graphs.modern = platformAgnosticGremlinServerDir + "/src/test/scripts/t
 settings.graphs.crew = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
 settings.graphs.grateful = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
 settings.graphs.sink = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
-if (testTransactions) settings.graphs.tx = platformAgnosticGremlinServerDir + "/src/test/scripts/neo4j-empty.properties"
+settings.graphs.tx = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkertransactiongraph-empty.properties"
 settings.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [platformAgnosticGremlinServerDir + "/src/test/scripts/generate-all.groovy"]
 settings.port = 45940
 
diff --git a/gremlin-server/src/test/scripts/tinkertransactiongraph-empty.properties b/gremlin-server/src/test/scripts/tinkertransactiongraph-empty.properties
new file mode 100644
index 0000000000..86103c100d
--- /dev/null
+++ b/gremlin-server/src/test/scripts/tinkertransactiongraph-empty.properties
@@ -0,0 +1,22 @@
+# 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.
+gremlin.graph=org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph
+gremlin.tinkergraph.vertexIdManager=INTEGER
+gremlin.tinkergraph.edgeIdManager=INTEGER
+gremlin.tinkergraph.vertexPropertyIdManager=LONG
+gremlin.tinkergraph.service=org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerTextSearchFactory
+gremlin.tinkergraph.service=org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerDegreeCentralityFactory
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/algorithm/generator/AbstractGeneratorTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/algorithm/generator/AbstractGeneratorTest.java
index acda7bd211..4b2969e3e0 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/algorithm/generator/AbstractGeneratorTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/algorithm/generator/AbstractGeneratorTest.java
@@ -58,11 +58,23 @@ public class AbstractGeneratorTest extends AbstractGremlinTest {
 
     private boolean sameInVertices(final Vertex v, final List<Vertex> inVertices) {
         return inVertices.stream()
-                .allMatch(inVertex -> IteratorUtils.filter(v.vertices(Direction.IN), hv -> hv.value("oid").equals(inVertex.value("oid"))).hasNext());
+                .allMatch(inVertex ->
+                {
+                    final Iterator<Vertex> itty = v.vertices(Direction.IN);
+                    final boolean result = IteratorUtils.filter(itty, hv -> hv.value("oid").equals(inVertex.value("oid"))).hasNext();
+                    CloseableIterator.closeIterator(itty);
+                    return result;
+                });
     }
 
     private boolean sameOutVertices(final Vertex v, final List<Vertex> outVertices) {
         return outVertices.stream()
-                .allMatch(outVertex -> IteratorUtils.filter(v.vertices(Direction.OUT), hv -> hv.value("oid").equals(outVertex.value("oid"))).hasNext());
+                .allMatch(outVertex ->
+                {
+                    final Iterator<Vertex> itty = v.vertices(Direction.OUT);
+                    final boolean result = IteratorUtils.filter(itty, hv -> hv.value("oid").equals(outVertex.value("oid"))).hasNext();
+                    CloseableIterator.closeIterator(itty);
+                    return result;
+                });
     }
 }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
index df7c95c76d..e99812aca6 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
@@ -27,7 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Property;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,8 +39,6 @@ import static org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexProper
 
 import java.util.Arrays;
 import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -55,10 +53,10 @@ public abstract class OrderabilityTest extends AbstractGremlinProcessTest {
         Date date = new Date();
         List list1 = Arrays.asList(1, 2, 3);
         List list2 = Arrays.asList(1, 2, 3, 4);
-        Set set1 = CollectionFactory.asSet(list1);
-        Set set2 = CollectionFactory.asSet(list2);
-        Map map1 = CollectionFactory.asMap(1, 11, 2, 22, 3, false, 4, 44);
-        Map map2 = CollectionFactory.asMap(1, 11, 2, 22, 33);
+        Set set1 = CollectionUtil.asSet(list1);
+        Set set2 = CollectionUtil.asSet(list2);
+        Map map1 = CollectionUtil.asMap(1, 11, 2, 22, 3, false, 4, 44);
+        Map map2 = CollectionUtil.asMap(1, 11, 2, 22, 33);
 
         Object[] unordered = { map2, 1, map1, "foo", null, list1, date, set1, list2, true, uuid, "bar", 2.0, false, set2 };
     }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeTest.java
index c308a9d6cb..226c0f32ce 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeTest.java
@@ -37,7 +37,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringEndsWith.endsWith;
 import static org.junit.Assert.assertEquals;
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexTest.java
index f7c0b16804..9185d3a41e 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexTest.java
@@ -33,7 +33,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/FeatureSupportTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/FeatureSupportTest.java
index 8381a76d9a..93ca3773ac 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/FeatureSupportTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/FeatureSupportTest.java
@@ -121,7 +121,8 @@ public class FeatureSupportTest {
          */
         @Test
         @FeatureRequirement(featureClass = GraphFeatures.class, feature = FEATURE_TRANSACTIONS, supported = false)
-        public void shouldSupportTransactionsIfAGraphConstructsATx() throws Exception {
+        @FeatureRequirement(featureClass = GraphFeatures.class, feature = FEATURE_THREADED_TRANSACTIONS, supported = false)
+        public void shouldSupportTransactionsIfAGraphConstructsATx() {
             try {
                 graph.tx();
                 fail(String.format(INVALID_FEATURE_SPECIFICATION, GraphFeatures.class.getSimpleName(), FEATURE_TRANSACTIONS));
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java
index 6d2cb35461..f1f6bb9725 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java
@@ -919,7 +919,7 @@ public class GraphTest extends AbstractGremlinTest {
         int counter = 0;
         for (Vertex v : vertices) {
             counter = counter + 1;
-            v.remove();
+            graph.vertices(v.id()).next().remove();
 
             if ((counter + 1) % 2 == 0) {
                 final int currentCounter = counter;
@@ -976,8 +976,8 @@ public class GraphTest extends AbstractGremlinTest {
     @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
     public void shouldEvaluateConnectivityPatterns() {
-        final Vertex a;
-        final Vertex b;
+        Vertex a;
+        Vertex b;
         final Vertex c;
         final Vertex d;
         if (graph.features().vertex().supportsUserSuppliedIds()) {
@@ -1011,10 +1011,10 @@ public class GraphTest extends AbstractGremlinTest {
         });
 
         if (graph.features().vertex().supportsUserSuppliedIds()) {
-            final Vertex va = graph.vertices(graphProvider.convertId("1", Vertex.class)).next();
-            final Vertex vb = graph.vertices(graphProvider.convertId("2", Vertex.class)).next();
-            final Vertex vc = graph.vertices(graphProvider.convertId("3", Vertex.class)).next();
-            final Vertex vd = graph.vertices(graphProvider.convertId("4", Vertex.class)).next();
+            Vertex va = graph.vertices(graphProvider.convertId("1", Vertex.class)).next();
+            Vertex vb = graph.vertices(graphProvider.convertId("2", Vertex.class)).next();
+            Vertex vc = graph.vertices(graphProvider.convertId("3", Vertex.class)).next();
+            Vertex vd = graph.vertices(graphProvider.convertId("4", Vertex.class)).next();
 
             assertEquals(a, va);
             assertEquals(b, vb);
@@ -1032,6 +1032,14 @@ public class GraphTest extends AbstractGremlinTest {
 
             final Edge i = a.addEdge(graphProvider.convertLabel("hates"), b);
 
+            a = graph.vertices(a.id()).next();
+            b = graph.vertices(b.id()).next();
+
+            va = graph.vertices(graphProvider.convertId("1", Vertex.class)).next();
+            vb = graph.vertices(graphProvider.convertId("2", Vertex.class)).next();
+            vc = graph.vertices(graphProvider.convertId("3", Vertex.class)).next();
+            vd = graph.vertices(graphProvider.convertId("4", Vertex.class)).next();
+
             assertEquals(1l, IteratorUtils.count(va.edges(Direction.IN)));
             assertEquals(2l, IteratorUtils.count(va.edges(Direction.OUT)));
             assertEquals(2l, IteratorUtils.count(vb.edges(Direction.IN)));
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/StructureStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/StructureStandardSuite.java
index 26f5b1b744..7e808d1841 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/StructureStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/StructureStandardSuite.java
@@ -105,6 +105,7 @@ public class StructureStandardSuite extends AbstractGremlinSuite {
             SerializationTest.class,
             StarGraphTest.class,
             TransactionTest.class,
+            TransactionMultiThreadedTest.class,
             VertexTest.class
     };
 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
new file mode 100644
index 0000000000..ee57728571
--- /dev/null
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
@@ -0,0 +1,674 @@
+/*
+ * 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;
+
+import org.apache.tinkerpop.gremlin.AbstractGremlinTest;
+import org.apache.tinkerpop.gremlin.FeatureRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.util.TransactionException;
+import org.junit.Test;
+
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.EdgeFeatures.FEATURE_REMOVE_EDGES;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.ElementFeatures.FEATURE_ADD_PROPERTY;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexFeatures.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for graphs that has multithreaded access
+ */
+public class TransactionMultiThreadedTest extends AbstractGremlinTest {
+
+    ///// vertex tests
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    public void shouldCommit() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().next();
+        gtx.addV().next();
+
+        assertEquals(2, (long) gtx.V().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(2L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_REMOVE_VERTICES)
+    public void shouldDeleteVertexOnCommit() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        gtx.tx().commit();
+
+        assertEquals(1, (long) gtx.V().count().next());
+
+        gtx.V(v1.id()).drop().iterate();
+        assertEquals(0, (long) gtx.V().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+
+        gtx.tx().commit();
+
+        assertEquals(0, (long) gtx.V().count().next());
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_REMOVE_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    public void shouldDeleteRelatedEdgesOnVertexDelete() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+
+        gtx.tx().commit();
+
+        assertEquals(2L, (long) gtx.V().count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        gtx.V(v1.id()).drop().iterate();
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(1L, (long) gtx3.V().count().next());
+        assertEquals(0L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_REMOVE_VERTICES)
+    public void shouldHandleConcurrentVertexDelete() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        gtx.tx().commit();
+
+        gtx.V(v1.id()).drop().iterate();
+
+        // other tx try to remove same vertex
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+
+            gtx2.V(v1.id()).drop().iterate();
+
+            // should be ok
+            assertEquals(0, (long) gtx2.V().count().next());
+            assertEquals(0, (long) gtx2.E().count().next());
+
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        // try do delete in initial tx
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+        assertEquals(0, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldChangeVertexProperty() {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        // create vertex with test property
+        final Vertex v1 = gtx.addV().property("test", 1).next();
+        gtx.tx().commit();
+
+        assertEquals(1, gtx.V(v1.id()).values("test").next());
+
+        // change test property
+        gtx.V(v1.id()).property("test", 2).iterate();
+        gtx.tx().commit();
+
+        // should be only 1 vertex with updated property
+        assertEquals(1L, (long) gtx.V().count().next());
+        assertEquals(2, gtx.V(v1.id()).values("test").next());
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldHandleConcurrentChangeForVertexProperty() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        gtx.tx().commit();
+
+        // change test property
+        gtx.V(v1.id()).property("test", 1).iterate();
+
+        // change property in other tx
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.V(v1.id()).property("test", 2).iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        // should be only 1 vertex with updated property
+        assertEquals(1L, (long) gtx.V().count().next());
+        assertEquals(2, gtx.V(v1.id()).values("test").next());
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_META_PROPERTIES)
+    public void shouldHandleConcurrentChangeForVertexMetaProperty() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().property("test", 1).next();
+        gtx.tx().commit();
+
+        // change meta property
+        gtx.V(v1.id()).properties("test").property("meta1", "tx1").iterate();
+
+        // change same property in other tx
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.V(v1.id()).properties("test").property("meta2", "tx2").iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            //fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        // should be only 1 vertex with updated property
+        assertEquals(1L, (long) gtx.V().count().next());
+        assertEquals("tx2", gtx.V(v1.id()).properties("test").values("meta1", "meta2").next());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldThrowExceptionWhenTryToAddVertexWithUsedId() {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        // add vertex
+        final Vertex v1 = gtx.addV().next();
+        gtx.tx().commit();
+
+        // try to add vertex with same id
+        gtx.addV().property(T.id, v1.id()).iterate();
+        gtx.tx().commit();
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    public void shouldRollbackAddedVertex() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().next();
+        gtx.addV().next();
+
+        assertEquals(2, (long) gtx.V().count().next());
+
+        // test count in second transaction before commit
+        countElementsInNewThreadTx(g, 0, 0);
+
+        gtx.tx().rollback();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    ///// transaction tests
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    public void shouldReopenClosedTransaction() {
+        final GraphTraversalSource gtx = g.tx().begin();
+        gtx.addV().iterate();
+        gtx.tx().commit();
+
+        gtx.addV().iterate();
+        assertTrue(gtx.tx().isOpen());
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx2 = g.tx().begin();
+        assertEquals(2L, (long) gtx2.V().count().next());
+    }
+
+    ///// Edge tests
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    public void shouldCommitEdge() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(2L, (long) gtx3.V().count().next());
+        assertEquals(1L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 1);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    public void shouldRollbackAddedEdge() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        // test count in second transaction before commit
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(0L, (long) gtx2.V().count().next());
+            assertEquals(0L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().rollback();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().count().next());
+        assertEquals(0L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_REMOVE_EDGES)
+    public void shouldDeleteEdgeOnCommit() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        gtx.E().hasLabel("tests").drop().iterate();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // test count in other thread transaction
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(2L, (long) gtx2.V().count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_REMOVE_EDGES)
+    public void shouldHandleConcurrentDeleteEdge() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        gtx.E().hasLabel("tests").drop().iterate();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // other tx try to remove same edge
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(2L, (long) gtx2.V().count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+
+            // try to remove edge
+            gtx2.E().hasLabel("tests").drop().iterate();
+
+            // should be ok
+            assertEquals(2, (long) gtx2.V().count().next());
+            assertEquals(0, (long) gtx2.E().count().next());
+
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        // try do delete in initial tx
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldHandleConcurrentChangeForProperty() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        final Edge edge = gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        // change test property
+        gtx.E(edge.id()).property("test", 1).iterate();
+
+        // change property in other tx
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.E(edge.id()).property("test", 2).iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        // should be only 1 edge with updated property
+        assertEquals(1L, (long) gtx.E(edge.id()).count().next());
+        assertEquals(2, gtx.E(edge.id()).values("test").next());
+    }
+
+
+    //tx1 adds a property to v1, tx2 deletes v1
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldHandleAddingPropertyWhenOtherTxDeleteVertex() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        gtx.tx().commit();
+
+        // tx1 try to add property
+        gtx.V(v1.id()).property("test", 1).iterate();
+
+        // tx2 in same time delete vertex used by tx1
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.V(v1.id()).drop().iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        assertEquals(0, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_PROPERTY)
+    public void shouldHandleAddingPropertyWhenOtherTxDeleteEdge() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        final Edge edge = gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        // tx1 try to add property
+        gtx.E(edge.id()).property("test", 1).iterate();
+
+        // tx2 in same time delete edge used by tx1
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.E(edge.id()).drop().iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    // tx1 adds an edge from v1 to v2, tx2 deletes v1 or v2
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_REMOVE_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = FEATURE_ADD_EDGES)
+    public void shouldHandleAddingEdgeWhenOtherTxDeleteVertex() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+        final Vertex v2 = gtx.addV().next();
+        gtx.tx().commit();
+
+        // tx1 try to add Edge
+        gtx.addE("test").from(v1).to(v2).iterate();
+
+        // tx2 in same time delete one of vertices used by tx1
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.V(v1.id()).drop().iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        assertEquals(1, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+    }
+
+    // tx1 adds a new vertex v1, tx2 adds the same vertex
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    public void shouldHandleAddingSameVertexInDifferentTx() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+
+        // tx2 in same time add vertex with same id
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.addV().property(T.id, v1.id()).iterate();
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        assertEquals(1, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+    }
+
+    // tx1 adds vertex v1, tx2 removes vertex v1
+    @Test
+    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = FEATURE_TRANSACTIONS)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ADD_VERTICES)
+    public void shouldHandleAddingVertexWhenOtherTxTryToDeleteSameVertex() throws InterruptedException {
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex v1 = gtx.addV().next();
+
+        // tx2 in same time try to delete same vertex
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            gtx2.V(v1.id()).drop().iterate();
+
+            assertEquals(0, (long) gtx.V().count().next());
+            assertEquals(0, (long) gtx.E().count().next());
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        assertEquals(1, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+    }
+
+    private void countElementsInNewThreadTx(final GraphTraversalSource g, final long verticesCount, final long edgesCount) throws InterruptedException {
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx = g.tx().begin();
+            assertEquals(verticesCount, (long) gtx.V().count().next());
+            assertEquals(edgesCount, (long) gtx.E().count().next());
+        });
+        thread.start();
+        thread.join();
+    }
+}
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionTest.java
index 76db416a83..29e9aa19e0 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionTest.java
@@ -23,9 +23,11 @@ import org.apache.tinkerpop.gremlin.ExceptionCoverage;
 import org.apache.tinkerpop.gremlin.FeatureRequirement;
 import org.apache.tinkerpop.gremlin.FeatureRequirementSet;
 import org.apache.commons.configuration2.Configuration;
+import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import org.junit.Test;
 
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
@@ -332,8 +334,8 @@ public class TransactionTest extends AbstractGremlinTest {
     @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
     @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
     public void shouldRollbackPropertyAutoTransactionByDefault() {
-        final Vertex v1 = graph.addVertex("name", "marko");
-        final Edge e1 = v1.addEdge("l", v1, "name", "xxx");
+        Vertex v1 = graph.addVertex("name", "marko");
+        Edge e1 = v1.addEdge("l", v1, "name", "xxx");
         assertVertexEdgeCounts(graph, 1, 1);
         assertEquals(v1.id(), graph.vertices(v1.id()).next().id());
         assertEquals(e1.id(), graph.edges(e1.id()).next().id());
@@ -344,6 +346,7 @@ public class TransactionTest extends AbstractGremlinTest {
         assertEquals("marko", v1.<String>value("name"));
         assertEquals("marko", graph.vertices(v1.id()).next().<String>value("name"));
 
+        v1 = graph.vertices(v1.id()).next();
         v1.property(VertexProperty.Cardinality.single, "name", "stephen");
 
         assertEquals("stephen", v1.<String>value("name"));
@@ -351,9 +354,11 @@ public class TransactionTest extends AbstractGremlinTest {
 
         g.tx().rollback();
 
+        v1 = graph.vertices(v1.id()).next();
         assertEquals("marko", v1.<String>value("name"));
         assertEquals("marko", graph.vertices(v1.id()).next().<String>value("name"));
 
+        e1 = graph.edges(e1.id()).next();
         e1.property("name", "yyy");
 
         assertEquals("yyy", e1.<String>value("name"));
@@ -361,6 +366,7 @@ public class TransactionTest extends AbstractGremlinTest {
 
         g.tx().rollback();
 
+        e1 = graph.edges(e1.id()).next();
         assertEquals("xxx", e1.<String>value("name"));
         assertEquals("xxx", graph.edges(e1.id()).next().<String>value("name"));
 
@@ -405,8 +411,10 @@ public class TransactionTest extends AbstractGremlinTest {
         t.start();
         t.join();
 
+        final Iterator<Vertex> vertices = graph.vertices(vid.get().id());
         // this was committed
-        assertThat(graph.vertices(vid.get().id()).hasNext(), is(true));
+        assertThat(vertices.hasNext(), is(true));
+        CloseableIterator.closeIterator(vertices);
 
         try {
             // this was not
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
index 2bad1a9a09..97652807d5 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
@@ -547,32 +547,36 @@ public class VertexPropertyTest extends AbstractGremlinTest {
                 assertEquals(0, IteratorUtils.count(stephen.properties("blah")));
             });
 
-            stephen.remove();
+            final Vertex marko2 = graph.vertices(marko.id()).next();
+            final Vertex stephen2 = graph.vertices(stephen.id()).next();
+            stephen2.remove();
             tryCommit(graph, graph -> {
                 assertVertexEdgeCounts(graph, 1, 0);
-                assertEquals(2, IteratorUtils.count(marko.properties("name")));
-                assertEquals(2, IteratorUtils.count(marko.properties()));
-                assertEquals(0, IteratorUtils.count(marko.properties("blah")));
+                assertEquals(2, IteratorUtils.count(marko2.properties("name")));
+                assertEquals(2, IteratorUtils.count(marko2.properties()));
+                assertEquals(0, IteratorUtils.count(marko2.properties("blah")));
             });
 
+            final Vertex marko3 = graph.vertices(marko.id()).next();
             for (int i = 0; i < 100; i++) {
-                marko.property(VertexProperty.Cardinality.list, "name", "Remove-" + String.valueOf(i));
+                marko3.property(VertexProperty.Cardinality.list, "name", "Remove-" + String.valueOf(i));
             }
             tryCommit(graph, graph -> {
                 assertVertexEdgeCounts(graph, 1, 0);
-                assertEquals(102, IteratorUtils.count(marko.properties("name")));
-                assertEquals(102, IteratorUtils.count(marko.properties()));
-                assertEquals(0, IteratorUtils.count(marko.properties("blah")));
+                assertEquals(102, IteratorUtils.count(marko3.properties("name")));
+                assertEquals(102, IteratorUtils.count(marko3.properties()));
+                assertEquals(0, IteratorUtils.count(marko3.properties("blah")));
             });
 
             g.V().properties("name").has(T.value, P.test((a, b) -> ((String) a).startsWith((String) b), "Remove-")).forEachRemaining(Property::remove);
+            final Vertex marko4 = graph.vertices(marko.id()).next();
             tryCommit(graph, graph -> {
                 assertVertexEdgeCounts(graph, 1, 0);
-                assertEquals(2, IteratorUtils.count(marko.properties("name")));
-                assertEquals(2, IteratorUtils.count(marko.properties()));
-                assertEquals(0, IteratorUtils.count(marko.properties("blah")));
+                assertEquals(2, IteratorUtils.count(marko4.properties("name")));
+                assertEquals(2, IteratorUtils.count(marko4.properties()));
+                assertEquals(0, IteratorUtils.count(marko4.properties("blah")));
             });
-            marko.remove();
+            marko4.remove();
             tryCommit(graph, getAssertVertexEdgeCounts(0, 0));
         }
     }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java
index a0e3f7a026..e347b86db0 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java
@@ -566,7 +566,7 @@ public class VertexTest {
             final List<Vertex> vertices = new ArrayList<>();
             IteratorUtils.fill(graph.vertices(), vertices);
             for (Vertex v : vertices) {
-                v.remove();
+                graph.vertices(v.id()).next().remove();
                 tryCommit(graph);
             }
 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoCustomTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoCustomTest.java
index d04083561e..1b86f4b54e 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoCustomTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoCustomTest.java
@@ -138,6 +138,8 @@ public class IoCustomTest extends AbstractGremlinTest {
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = FEATURE_ANY_IDS)
     public void shouldProperlySerializeCustomId() throws Exception {
         graph.addVertex(T.id, new CustomId("vertex", UUID.fromString("AF4B5965-B176-4552-B3C1-FBBE2F52C305")));
+        if (graph.features().graph().supportsTransactions())
+            graph.tx().commit();
 
         final GraphWriter writer = writerMaker.apply(graph);
         final GraphReader reader = readerMaker.apply(graph);
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoTest.java
index f99ea6bc76..82828c4c97 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/IoTest.java
@@ -311,6 +311,8 @@ public class IoTest {
                 writer.writeGraph(out, graph);
 
                 graph.vertices().forEachRemaining(Element::remove);
+                if (graph.features().graph().supportsTransactions())
+                    graph.tx().commit();
                 try (final InputStream stream = new ByteArrayInputStream(((ByteArrayOutputStream) out).toByteArray())) {
                     reader.readGraph(stream, graph);
                 }
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV1Test.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV1Test.java
index 81440b4393..c43ba24bed 100644
--- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV1Test.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV1Test.java
@@ -279,7 +279,7 @@ public class GraphSONMessageSerializerV1Test {
         //check Vertex property
         final Map<String, Map<String, Map>> subTreeMap =  deserializedMap.get("1").get("value");
         final Map<String, Map<String, Map>> subTreeMap2 = subTreeMap.get("2").get("value");
-        final Map<String, String> vertexPropertiesDeep = subTreeMap2.get("3").get("key");
+        final Map<String, String> vertexPropertiesDeep = subTreeMap2.get("2").get("key");
         assertEquals("vadas", vertexPropertiesDeep.get("value"));
         assertEquals("name", vertexPropertiesDeep.get("label"));
 
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
index 97cce80594..9ba58a8fd0 100644
--- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONMessageSerializerV3Test.java
@@ -288,9 +288,9 @@ public class GraphSONMessageSerializerV3Test {
         Iterator<Vertex> vertexKeys = firstTree.keySet().iterator();
 
         Map expectedValues = new HashMap<Integer, String>() {{
-            put(3, "vadas");
-            put(5, "lop");
-            put(7, "josh");
+            put(2, "vadas");
+            put(4, "lop");
+            put(6, "josh");
         }};
         
         Map actualValues = new HashMap<Integer, String>();
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONUntypedMessageSerializerV1Test.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONUntypedMessageSerializerV1Test.java
index 4eec081b3b..023b4714d0 100644
--- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONUntypedMessageSerializerV1Test.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/GraphSONUntypedMessageSerializerV1Test.java
@@ -457,12 +457,12 @@ public class GraphSONUntypedMessageSerializerV1Test {
                                  .get("value")
                                  .get("2")
                                  .get("value")
-                                 .get("3").get("key").get("value").asText());
+                                 .get("2").get("key").get("value").asText());
         assertEquals("name", converted.get("1")
                                  .get("value")
                                  .get("2")
                                  .get("value")
-                                 .get("3").get("key").get("label").asText());
+                                 .get("2").get("key").get("label").asText());
         
         // check subitem
         assertEquals("lop", converted.get("1")
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
index 87dda07682..b1e5ac78b9 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
@@ -58,6 +58,11 @@ import java.util.stream.Stream;
 @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD)
 @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_STANDARD)
 @Graph.OptIn("org.apache.tinkerpop.gremlin.neo4j.NativeNeo4jSuite")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest",
+        method = "*",
+        reason = "Some scenarios are supported by Neo4jGraph")
+
 public final class Neo4jGraph implements Graph, WrappedGraph<Neo4jGraphAPI> {
 
     static {
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputer.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputer.java
index bf6f594166..ce9a57959d 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputer.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputer.java
@@ -35,6 +35,7 @@ import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
 
@@ -66,7 +67,7 @@ public final class TinkerGraphComputer implements GraphComputer {
     private Persist persist = null;
 
     private VertexProgram<?> vertexProgram;
-    private final TinkerGraph graph;
+    private final AbstractTinkerGraph graph;
     private TinkerMemory memory;
     private final TinkerMessageBoard messageBoard = new TinkerMessageBoard();
     private boolean executed = false;
@@ -82,7 +83,7 @@ public final class TinkerGraphComputer implements GraphComputer {
      */
     private final ExecutorService computerService = Executors.newSingleThreadExecutor(threadFactoryBoss);
 
-    public TinkerGraphComputer(final TinkerGraph graph) {
+    public TinkerGraphComputer(final AbstractTinkerGraph graph) {
         this.graph = graph;
     }
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
index 43998fb292..3c523ef4d1 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
@@ -30,6 +30,7 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex;
@@ -49,14 +50,14 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public final class TinkerGraphComputerView {
 
-    private final TinkerGraph graph;
+    private final AbstractTinkerGraph graph;
     protected final Map<String, VertexComputeKey> computeKeys;
     private Map<Element, Map<String, List<VertexProperty<?>>>> computeProperties;
     private final Set<Object> legalVertices = new HashSet<>();
     private final Map<Object, Set<Object>> legalEdges = new HashMap<>();
     private final GraphFilter graphFilter;
 
-    public TinkerGraphComputerView(final TinkerGraph graph, final GraphFilter graphFilter, final Set<VertexComputeKey> computeKeys) {
+    public TinkerGraphComputerView(final AbstractTinkerGraph graph, final GraphFilter graphFilter, final Set<VertexComputeKey> computeKeys) {
         this.graph = graph;
         this.computeKeys = new HashMap<>();
         computeKeys.forEach(key -> this.computeKeys.put(key.getKey(), key));
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerWorkerPool.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerWorkerPool.java
index 637b416548..6ff24c30f6 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerWorkerPool.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerWorkerPool.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.computer.VertexProgram;
 import org.apache.tinkerpop.gremlin.process.computer.util.MapReducePool;
 import org.apache.tinkerpop.gremlin.process.computer.util.VertexProgramPool;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
 import org.apache.tinkerpop.gremlin.util.function.TriConsumer;
@@ -56,7 +57,7 @@ public final class TinkerWorkerPool implements AutoCloseable {
     private final Queue<TinkerWorkerMemory> workerMemoryPool = new ConcurrentLinkedQueue<>();
     private final List<List<Vertex>> workerVertices = new ArrayList<>();
 
-    public TinkerWorkerPool(final TinkerGraph graph, final TinkerMemory memory, final int numberOfWorkers) {
+    public TinkerWorkerPool(final AbstractTinkerGraph graph, final TinkerMemory memory, final int numberOfWorkers) {
         this.numberOfWorkers = numberOfWorkers;
         this.workerPool = Executors.newFixedThreadPool(numberOfWorkers, THREAD_FACTORY_WORKER);
         this.completionService = new ExecutorCompletionService<>(this.workerPool);
@@ -64,7 +65,7 @@ public final class TinkerWorkerPool implements AutoCloseable {
             this.workerMemoryPool.add(new TinkerWorkerMemory(memory));
             this.workerVertices.add(new ArrayList<>());
         }
-        int batchSize = TinkerHelper.getVertices(graph).size() / this.numberOfWorkers;
+        int batchSize = graph.getVerticesCount() / this.numberOfWorkers;
         if (0 == batchSize)
             batchSize = 1;
         int counter = 0;
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerCountGlobalStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerCountGlobalStep.java
index e0e7b80510..786e09e375 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerCountGlobalStep.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerCountGlobalStep.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementExce
 import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
 
@@ -49,10 +50,10 @@ public final class TinkerCountGlobalStep<S extends Element> extends AbstractStep
     protected Traverser.Admin<Long> processNextStart() throws NoSuchElementException {
         if (!this.done) {
             this.done = true;
-            final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+            final AbstractTinkerGraph graph = (AbstractTinkerGraph) this.getTraversal().getGraph().get();
             return this.getTraversal().getTraverserGenerator().generate(Vertex.class.isAssignableFrom(this.elementClass) ?
-                            (long) TinkerHelper.getVertices(graph).size() :
-                            (long) TinkerHelper.getEdges(graph).size(),
+                            (long) graph.getVerticesCount() :
+                            (long) graph.getEdgesCount(),
                     (Step) this, 1L);
         } else
             throw FastNoSuchElementException.instance();
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
index 80242dc38d..6a3e965629 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
@@ -22,7 +22,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Compare;
 import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.BinaryReductionStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
@@ -32,9 +31,9 @@ import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraphIterator;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIndexHelper;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.ArrayList;
@@ -69,7 +68,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
     }
 
     private Iterator<? extends Edge> edges() {
-        final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) this.getTraversal().getGraph().get();
         final HasContainer indexedContainer = getIndexKey(Edge.class);
         Iterator<Edge> iterator;
         // ids are present, filter on them first
@@ -80,7 +79,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
         else
             iterator = null == indexedContainer ?
                     this.iteratorList(graph.edges()) :
-                    TinkerHelper.queryEdgeIndex(graph, indexedContainer.getKey(), indexedContainer.getPredicate().getValue()).stream()
+                    TinkerIndexHelper.queryEdgeIndex(graph, indexedContainer.getKey(), indexedContainer.getPredicate().getValue()).stream()
                                 .filter(edge -> HasContainer.testAll(edge, this.hasContainers))
                                 .collect(Collectors.<Edge>toList()).iterator();
 
@@ -91,7 +90,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
     }
 
     private Iterator<? extends Vertex> vertices() {
-        final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) this.getTraversal().getGraph().get();
         final HasContainer indexedContainer = getIndexKey(Vertex.class);
         Iterator<? extends Vertex> iterator;
         // ids are present, filter on them first
@@ -102,7 +101,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
         else
             iterator = (null == indexedContainer ?
                     this.iteratorList(graph.vertices()) :
-                    IteratorUtils.filter(TinkerHelper.queryVertexIndex(graph, indexedContainer.getKey(), indexedContainer.getPredicate().getValue()).iterator(),
+                    IteratorUtils.filter(TinkerIndexHelper.queryVertexIndex(graph, indexedContainer.getKey(), indexedContainer.getPredicate().getValue()).iterator(),
                                          vertex -> HasContainer.testAll(vertex, this.hasContainers)));
 
         iterators.add(iterator);
@@ -111,7 +110,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
     }
 
     private HasContainer getIndexKey(final Class<? extends Element> indexedClass) {
-        final Set<String> indexedKeys = ((TinkerGraph) this.getTraversal().getGraph().get()).getIndexedKeys(indexedClass);
+        final Set<String> indexedKeys = ((AbstractTinkerGraph) this.getTraversal().getGraph().get()).getIndexedKeys(indexedClass);
 
         final Iterator<HasContainer> itty = IteratorUtils.filter(hasContainers.iterator(),
                 c -> c.getPredicate().getBiPredicate() == Compare.eq && indexedKeys.contains(c.getKey()));
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphCountStrategy.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphCountStrategy.java
index 8182e7047f..1002bb520d 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphCountStrategy.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphCountStrategy.java
@@ -29,8 +29,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectStep;
-import org.apache.tinkerpop.gremlin.process.traversal.step.util.CollectingBarrierStep;
-import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
 import org.apache.tinkerpop.gremlin.structure.Element;
@@ -41,8 +39,8 @@ import java.util.List;
 import java.util.Set;
 
 /**
- * This strategy will do a direct {@link org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper#getVertices}
- * size call if the traversal is a count of the vertices and edges of the graph or a one-to-one map chain thereof.
+ * This strategy will do a graph size call if the traversal is a count of the vertices and edges of the graph
+ * or a one-to-one map chain thereof.
  *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @example <pre>
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerDegreeCentralityFactory.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerDegreeCentralityFactory.java
index c0e0cae991..1586f0d5f3 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerDegreeCentralityFactory.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerDegreeCentralityFactory.java
@@ -24,7 +24,7 @@ import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.service.Service;
 import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
@@ -32,7 +32,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.LongStream;
 
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 
 /**
  * Count the IN/OUT/BOTH edges for a set of vertices. Demonstrates a {@link Service.Type#Streaming} service.
@@ -52,7 +52,7 @@ public class TinkerDegreeCentralityFactory extends TinkerServiceRegistry.TinkerS
         );
     }
 
-    public TinkerDegreeCentralityFactory(final TinkerGraph graph) {
+    public TinkerDegreeCentralityFactory(final AbstractTinkerGraph graph) {
         super(graph, NAME);
     }
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerServiceRegistry.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerServiceRegistry.java
index efc09b0fb9..69afd290e0 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerServiceRegistry.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerServiceRegistry.java
@@ -24,13 +24,11 @@ import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSe
 import org.apache.tinkerpop.gremlin.structure.service.Service;
 import org.apache.tinkerpop.gremlin.structure.service.ServiceRegistry;
 import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.util.function.TriFunction;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -45,9 +43,9 @@ import java.util.function.BiFunction;
  */
 public class TinkerServiceRegistry extends ServiceRegistry {
 
-    private final TinkerGraph graph;
+    private final AbstractTinkerGraph graph;
 
-    public TinkerServiceRegistry(final TinkerGraph graph) {
+    public TinkerServiceRegistry(final AbstractTinkerGraph graph) {
         this.graph = graph;
     }
 
@@ -61,12 +59,12 @@ public class TinkerServiceRegistry extends ServiceRegistry {
     }
 
     public abstract static class TinkerServiceFactory<I, R> implements ServiceFactory<I, R> {
-        protected final TinkerGraph graph;
+        protected final AbstractTinkerGraph graph;
         protected final String name;
         protected final Map describeParams = new LinkedHashMap();
         protected final Map<Type,Set<TraverserRequirement>> requirements = new LinkedHashMap<>();
 
-        protected TinkerServiceFactory(final TinkerGraph graph, final String name) {
+        protected TinkerServiceFactory(final AbstractTinkerGraph graph, final String name) {
             this.graph = graph;
             this.name = name;
         }
@@ -134,7 +132,7 @@ public class TinkerServiceRegistry extends ServiceRegistry {
 
         private Map<Type, Service<I, R>> lambdas = new LinkedHashMap<>();
 
-        public LambdaServiceFactory(final TinkerGraph graph, final String name) {
+        public LambdaServiceFactory(final AbstractTinkerGraph graph, final String name) {
             super(graph, name);
         }
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerTextSearchFactory.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerTextSearchFactory.java
index 8861a3aa5d..97085911e6 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerTextSearchFactory.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/services/TinkerTextSearchFactory.java
@@ -24,7 +24,7 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.service.Service;
 import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
 
 import java.util.Collections;
@@ -33,7 +33,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 
 /**
  * Inefficient text search implementation (scan+filter), searches for {@link Property}s by token/regex.
@@ -79,7 +79,7 @@ public class TinkerTextSearchFactory<I, R> extends TinkerServiceRegistry.TinkerS
         }
     }
 
-    public TinkerTextSearchFactory(final TinkerGraph graph) {
+    public TinkerTextSearchFactory(final AbstractTinkerGraph graph) {
         super(graph, NAME);
     }
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
similarity index 53%
copy from tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
copy to tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
index 6110ab785c..237be062aa 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
@@ -18,10 +18,8 @@
  */
 package org.apache.tinkerpop.gremlin.tinkergraph.structure;
 
-import org.apache.commons.configuration2.BaseConfiguration;
 import org.apache.commons.configuration2.Configuration;
 import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
-import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Graph;
@@ -32,56 +30,29 @@ import org.apache.tinkerpop.gremlin.structure.io.Io;
 import org.apache.tinkerpop.gremlin.structure.io.IoCore;
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
 import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoVersion;
-import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
-import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView;
-import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy;
-import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy;
 import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry;
-import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Stream;
 
-import static org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry.TinkerServiceFactory;
-
 /**
- * An in-memory (with optional persistence on calls to {@link #close()}), reference implementation of the property
- * graph interfaces provided by TinkerPop.
+ * Base class for {@link TinkerGraph} and {@link TinkerTransactionGraph}.
+ * Contains common methods, variables and constants, but leaves the work with elements and indices
+ * to concrete implementations.
  *
- * @author Marko A. Rodriguez (http://markorodriguez.com)
- * @author Stephen Mallette (http://stephen.genoprime.com)
+ * @author Valentyn Kahamlyk
  */
-@Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_STANDARD)
-@Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_INTEGRATE)
-@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD)
-@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER)
-@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_STANDARD)
-@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_COMPUTER)
-public final class TinkerGraph implements Graph {
-
-    static {
-        TraversalStrategies.GlobalCache.registerStrategies(TinkerGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies(
-                TinkerGraphStepStrategy.instance(),
-                TinkerGraphCountStrategy.instance()));
-    }
-
-    private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{
-        this.setProperty(Graph.GRAPH, TinkerGraph.class.getName());
-    }};
+public abstract class AbstractTinkerGraph implements Graph {
 
     public static final String GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER = "gremlin.tinkergraph.vertexIdManager";
     public static final String GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER = "gremlin.tinkergraph.edgeIdManager";
@@ -92,188 +63,114 @@ public final class TinkerGraph implements Graph {
     public static final String GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES = "gremlin.tinkergraph.allowNullPropertyValues";
     public static final String GREMLIN_TINKERGRAPH_SERVICE = "gremlin.tinkergraph.service";
 
-    private final TinkerGraphFeatures features = new TinkerGraphFeatures();
 
     protected AtomicLong currentId = new AtomicLong(-1L);
-    protected Map<Object, Vertex> vertices = new ConcurrentHashMap<>();
-    protected Map<Object, Edge> edges = new ConcurrentHashMap<>();
 
     protected TinkerGraphVariables variables = null;
     protected TinkerGraphComputerView graphComputerView = null;
-    protected TinkerIndex<TinkerVertex> vertexIndex = null;
-    protected TinkerIndex<TinkerEdge> edgeIndex = null;
+    protected AbstractTinkerIndex<TinkerVertex> vertexIndex = null;
+    protected AbstractTinkerIndex<TinkerEdge> edgeIndex = null;
 
-    protected IdManager<?> vertexIdManager;
-    protected IdManager<?> edgeIdManager;
-    protected IdManager<?> vertexPropertyIdManager;
-    protected final VertexProperty.Cardinality defaultVertexPropertyCardinality;
-    protected final boolean allowNullPropertyValues;
+    protected IdManager<Vertex> vertexIdManager;
+    protected IdManager<Edge> edgeIdManager;
+    protected IdManager<VertexProperty> vertexPropertyIdManager;
+    protected VertexProperty.Cardinality defaultVertexPropertyCardinality;
+    protected boolean allowNullPropertyValues;
 
-    protected final TinkerServiceRegistry serviceRegistry;
+    protected TinkerServiceRegistry serviceRegistry;
 
-    private final Configuration configuration;
-    private final String graphLocation;
-    private final String graphFormat;
+    protected Configuration configuration;
+    protected String graphLocation;
+    protected String graphFormat;
 
     /**
-     * An empty private constructor that initializes {@link TinkerGraph}.
+     * {@inheritDoc}
      */
-    private TinkerGraph(final Configuration configuration) {
-        this.configuration = configuration;
-        vertexIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, Vertex.class);
-        edgeIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, Edge.class);
-        vertexPropertyIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, VertexProperty.class);
-        defaultVertexPropertyCardinality = VertexProperty.Cardinality.valueOf(
-                configuration.getString(GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.single.name()));
-        allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, false);
-
-        graphLocation = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null);
-        graphFormat = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_FORMAT, null);
-
-        if ((graphLocation != null && null == graphFormat) || (null == graphLocation && graphFormat != null))
-            throw new IllegalStateException(String.format("The %s and %s must both be specified if either is present",
-                    GREMLIN_TINKERGRAPH_GRAPH_LOCATION, GREMLIN_TINKERGRAPH_GRAPH_FORMAT));
-
-        if (graphLocation != null) loadGraph();
-
-        serviceRegistry = new TinkerServiceRegistry(this);
-        configuration.getList(String.class, GREMLIN_TINKERGRAPH_SERVICE, Collections.emptyList()).forEach(serviceClass ->
-                serviceRegistry.registerService(instantiate(serviceClass)));
-    }
+    public abstract Vertex addVertex(final Object... keyValues);
 
     /**
-     * Open a new {@link TinkerGraph} instance.
-     * <p/>
-     * <b>Reference Implementation Help:</b> If a {@link Graph} implementation does not require a {@code Configuration}
-     * (or perhaps has a default configuration) it can choose to implement a zero argument
-     * {@code open()} method. This is an optional constructor method for TinkerGraph. It is not enforced by the Gremlin
-     * Test Suite.
+     * {@inheritDoc}
      */
-    public static TinkerGraph open() {
-        return open(EMPTY_CONFIGURATION);
-    }
+    public abstract void removeVertex(final Object vertexId);
 
     /**
-     * Open a new {@code TinkerGraph} instance.
-     * <p/>
-     * <b>Reference Implementation Help:</b> This method is the one use by the {@link GraphFactory} to instantiate
-     * {@link Graph} instances.  This method must be overridden for the Structure Test Suite to pass. Implementers have
-     * latitude in terms of how exceptions are handled within this method.  Such exceptions will be considered
-     * implementation specific by the test suite as all test generate graph instances by way of
-     * {@link GraphFactory}. As such, the exceptions get generalized behind that facade and since
-     * {@link GraphFactory} is the preferred method to opening graphs it will be consistent at that level.
-     *
-     * @param configuration the configuration for the instance
-     * @return a newly opened {@link Graph}
+     * {@inheritDoc}
      */
-    public static TinkerGraph open(final Configuration configuration) {
-        return new TinkerGraph(configuration);
-    }
-
-    ////////////// STRUCTURE API METHODS //////////////////
-
-    @Override
-    public Vertex addVertex(final Object... keyValues) {
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
-        Object idValue = vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
-        final String label = ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL);
-
-        if (null != idValue) {
-            if (this.vertices.containsKey(idValue))
-                throw Exceptions.vertexWithIdAlreadyExists(idValue);
-        } else {
-            idValue = vertexIdManager.getNextId(this);
-        }
+    public abstract Edge addEdge(final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues);
 
-        final Vertex vertex = new TinkerVertex(idValue, label, this);
-        this.vertices.put(vertex.id(), vertex);
-
-        ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, keyValues);
-        return vertex;
-    }
-
-    @Override
-    public <C extends GraphComputer> C compute(final Class<C> graphComputerClass) {
-        if (!graphComputerClass.equals(TinkerGraphComputer.class))
-            throw Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(graphComputerClass);
-        return (C) new TinkerGraphComputer(this);
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public abstract void removeEdge(final Object edgeId);
 
-    @Override
-    public GraphComputer compute() {
-        return new TinkerGraphComputer(this);
-    }
+    /**
+     * Mark {@link Vertex} as changed in transaction.
+     * If the graph does not support transactions, then does nothing.
+     * @param vertex
+     */
+    public void touch(final TinkerVertex vertex) {};
 
-    @Override
-    public Variables variables() {
-        if (null == this.variables)
-            this.variables = new TinkerGraphVariables();
-        return this.variables;
-    }
+    /**
+     * Mark {@link Edge} as changed in transaction.
+     * If the graph does not support transactions, then does nothing.
+     * @param edge
+     */
+    public void touch(final TinkerEdge edge) {};
 
-    @Override
-    public <I extends Io> I io(final Io.Builder<I> builder) {
-        if (builder.requiresVersion(GryoVersion.V1_0) || builder.requiresVersion(GraphSONVersion.V1_0))
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV1.instance())).create();
-        else if (builder.requiresVersion(GraphSONVersion.V2_0))   // there is no gryo v2
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV2.instance())).create();
-        else
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV3.instance())).create();
-    }
+    /**
+     * Return {@link Vertex} by id.
+     * Does not create an iterator, so is the preferred method when only 1 element needs to be returned.
+     * @param vertexId
+     * @return Vertex
+     */
+    public abstract Vertex vertex(final Object vertexId);
 
-    @Override
-    public String toString() {
-        return StringFactory.graphString(this, "vertices:" + this.vertices.size() + " edges:" + this.edges.size());
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public abstract Iterator<Vertex> vertices(final Object... vertexIds);
 
-    public void clear() {
-        this.vertices.clear();
-        this.edges.clear();
-        this.variables = null;
-        this.currentId.set(-1L);
-        this.vertexIndex = null;
-        this.edgeIndex = null;
-        this.graphComputerView = null;
-    }
+    /**
+     * Return {@link Edge} by id.
+     * Does not create an iterator, so is the preferred method when only 1 element needs to be returned.
+     * @param edgeId
+     * @return Edge
+     */
+    public abstract Edge edge(final Object edgeId);
 
     /**
-     * This method only has an effect if the {@link #GREMLIN_TINKERGRAPH_GRAPH_LOCATION} is set, in which case the
-     * data in the graph is persisted to that location. This method may be called multiple times and does not release
-     * resources.
+     * {@inheritDoc}
      */
-    @Override
-    public void close() {
-        if (graphLocation != null) saveGraph();
-        // shutdown services
-        serviceRegistry.close();
-    }
+    public abstract Iterator<Edge> edges(final Object... edgeIds);
 
-    @Override
-    public Transaction tx() {
-        throw Exceptions.transactionsNotSupported();
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public abstract Transaction tx();
 
-    @Override
-    public Configuration configuration() {
-        return configuration;
-    }
+    /**
+     * Graph-specific implementation for number of vertices.
+     * @return count of vertices in Graph.
+     */
+    public abstract int getVerticesCount();
 
-    @Override
-    public TinkerServiceRegistry getServiceRegistry() {
-        return serviceRegistry;
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public abstract boolean hasVertex(final Object id);
 
-    @Override
-    public Iterator<Vertex> vertices(final Object... vertexIds) {
-        return createElementIterator(Vertex.class, vertices, vertexIdManager, vertexIds);
-    }
+    /**
+     * Graph-specific implementation for number of vertices.
+     * @return count of vertices in Graph.
+     */
+    public abstract int getEdgesCount();
 
-    @Override
-    public Iterator<Edge> edges(final Object... edgeIds) {
-        return createElementIterator(Edge.class, edges, edgeIdManager, edgeIds);
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public abstract boolean hasEdge(final Object id);
 
-    private void loadGraph() {
+    protected void loadGraph() {
         final File f = new File(graphLocation);
         if (f.exists() && f.isFile()) {
             try {
@@ -292,7 +189,7 @@ public final class TinkerGraph implements Graph {
         }
     }
 
-    private void saveGraph() {
+    protected void saveGraph() {
         final File f = new File(graphLocation);
         if (f.exists()) {
             f.delete();
@@ -320,80 +217,77 @@ public final class TinkerGraph implements Graph {
         }
     }
 
-    private <T extends Element> Iterator<T> createElementIterator(final Class<T> clazz, final Map<Object, T> elements,
-                                                                  final IdManager idManager,
-                                                                  final Object... ids) {
-        final Iterator<T> iterator;
-        if (0 == ids.length) {
-            iterator = new TinkerGraphIterator<>(elements.values().iterator());
-        } else {
-            final List<Object> idList = Arrays.asList(ids);
-
-            // TinkerGraph can take a Vertex/Edge or any object as an "id". If it is an Element then we just cast
-            // to that type and pop off the identifier. there is no need to pass that through the IdManager since
-            // the assumption is that if it's already an Element, its identifier must be valid to the Graph and to
-            // its associated IdManager. All other objects are passed to the IdManager for conversion.
-            return new TinkerGraphIterator<>(IteratorUtils.filter(IteratorUtils.map(idList, id -> {
-                // ids cant be null so all of those filter out
-                if (null == id) return null;
-                final Object iid = clazz.isAssignableFrom(id.getClass()) ? clazz.cast(id).id() : idManager.convert(id);
-                return elements.get(idManager.convert(iid));
-            }).iterator(), Objects::nonNull));
-        }
-        return TinkerHelper.inComputerMode(this) ?
-                (Iterator<T>) (clazz.equals(Vertex.class) ?
-                        IteratorUtils.filter((Iterator<Vertex>) iterator, t -> this.graphComputerView.legalVertex(t)) :
-                        IteratorUtils.filter((Iterator<Edge>) iterator, t -> this.graphComputerView.legalEdge(t.outVertex(), t))) :
-                iterator;
-    }
 
-    /**
-     * Return TinkerGraph feature set.
-     * <p/>
-     * <b>Reference Implementation Help:</b> Implementers only need to implement features for which there are
-     * negative or instance configured features.  By default, all {@link Graph.Features} return true.
-     */
     @Override
-    public Features features() {
-        return features;
+    public <I extends Io> I io(final Io.Builder<I> builder) {
+        if (builder.requiresVersion(GryoVersion.V1_0) || builder.requiresVersion(GraphSONVersion.V1_0))
+            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV1.instance())).create();
+        else if (builder.requiresVersion(GraphSONVersion.V2_0))   // there is no gryo v2
+            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV2.instance())).create();
+        else
+            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV3.instance())).create();
     }
 
-    public class TinkerGraphFeatures implements Features {
-
-        private final TinkerGraphGraphFeatures graphFeatures = new TinkerGraphGraphFeatures();
-        private final TinkerGraphEdgeFeatures edgeFeatures = new TinkerGraphEdgeFeatures();
-        private final TinkerGraphVertexFeatures vertexFeatures = new TinkerGraphVertexFeatures();
+    ////////////// STRUCTURE API METHODS //////////////////
+    public <C extends GraphComputer> C compute(final Class<C> graphComputerClass) {
+        if (!graphComputerClass.equals(TinkerGraphComputer.class))
+            throw Exceptions.graphDoesNotSupportProvidedGraphComputer(graphComputerClass);
+        return (C) new TinkerGraphComputer(this);
+    }
 
-        private TinkerGraphFeatures() {
-        }
+    public GraphComputer compute() { return new TinkerGraphComputer(this); }
 
-        @Override
-        public GraphFeatures graph() {
-            return graphFeatures;
-        }
+    public Variables variables() {
+        if (null == this.variables)
+            this.variables = new TinkerGraphVariables();
+        return this.variables;
+    }
 
-        @Override
-        public EdgeFeatures edge() {
-            return edgeFeatures;
-        }
+    @Override
+    public String toString() {
+        return StringFactory.graphString(this, "vertices:" + this.getVerticesCount() + " edges:" + this.getEdgesCount());
+    }
 
-        @Override
-        public VertexFeatures vertex() {
-            return vertexFeatures;
-        }
+    /**
+     * Clear internal graph data
+     */
+    public void clear() {
+        this.variables = null;
+        this.currentId.set(-1L);
+        this.vertexIndex = null;
+        this.edgeIndex = null;
+        this.graphComputerView = null;
+    }
 
-        @Override
-        public String toString() {
-            return StringFactory.featureString(this);
-        }
+    /**
+     * This method only has an effect if the {@link TinkerGraph#GREMLIN_TINKERGRAPH_GRAPH_LOCATION} is set, in which case the
+     * data in the graph is persisted to that location. This method may be called multiple times and does not release
+     * resources.
+     */
+    @Override
+    public void close() {
+        if (graphLocation != null) saveGraph();
+        // shutdown services
+        serviceRegistry.close();
+    }
 
+    @Override
+    public Configuration configuration() {
+        return configuration;
     }
 
+    ///////////// Utility methods ///////////////
+    protected abstract void addOutEdge(final TinkerVertex vertex, final String label, final Edge edge);
+
+    protected abstract void addInEdge(final TinkerVertex vertex, final String label, final Edge edge);
+
+    ///////////// Features ///////////////
+
     public class TinkerGraphVertexFeatures implements Features.VertexFeatures {
 
-        private final TinkerGraphVertexPropertyFeatures vertexPropertyFeatures = new TinkerGraphVertexPropertyFeatures();
+        private final TinkerTransactionGraph.TinkerGraphVertexPropertyFeatures vertexPropertyFeatures = new TinkerTransactionGraph.TinkerGraphVertexPropertyFeatures();
 
-        private TinkerGraphVertexFeatures() {
+        protected TinkerGraphVertexFeatures() {
         }
 
         @Override
@@ -424,7 +318,7 @@ public final class TinkerGraph implements Graph {
 
     public class TinkerGraphEdgeFeatures implements Features.EdgeFeatures {
 
-        private TinkerGraphEdgeFeatures() {
+        protected TinkerGraphEdgeFeatures() {
         }
 
         @Override
@@ -443,36 +337,9 @@ public final class TinkerGraph implements Graph {
         }
     }
 
-    public class TinkerGraphGraphFeatures implements Features.GraphFeatures {
-
-        private TinkerGraphGraphFeatures() {
-        }
-
-        @Override
-        public boolean supportsConcurrentAccess() {
-            return false;
-        }
-
-        @Override
-        public boolean supportsTransactions() {
-            return false;
-        }
-
-        @Override
-        public boolean supportsThreadedTransactions() {
-            return false;
-        }
-
-        @Override
-        public boolean supportsServiceCall() {
-            return true;
-        }
-
-    }
-
     public class TinkerGraphVertexPropertyFeatures implements Features.VertexPropertyFeatures {
 
-        private TinkerGraphVertexPropertyFeatures() {
+        protected TinkerGraphVertexPropertyFeatures() {
         }
 
         @Override
@@ -493,44 +360,6 @@ public final class TinkerGraph implements Graph {
 
     ///////////// GRAPH SPECIFIC INDEXING METHODS ///////////////
 
-    /**
-     * Create an index for said element class ({@link Vertex} or {@link Edge}) and said property key.
-     * Whenever an element has the specified key mutated, the index is updated.
-     * When the index is created, all existing elements are indexed to ensure that they are captured by the index.
-     *
-     * @param key          the property key to index
-     * @param elementClass the element class to index
-     * @param <E>          The type of the element class
-     */
-    public <E extends Element> void createIndex(final String key, final Class<E> elementClass) {
-        if (Vertex.class.isAssignableFrom(elementClass)) {
-            if (null == this.vertexIndex) this.vertexIndex = new TinkerIndex<>(this, TinkerVertex.class);
-            this.vertexIndex.createKeyIndex(key);
-        } else if (Edge.class.isAssignableFrom(elementClass)) {
-            if (null == this.edgeIndex) this.edgeIndex = new TinkerIndex<>(this, TinkerEdge.class);
-            this.edgeIndex.createKeyIndex(key);
-        } else {
-            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
-        }
-    }
-
-    /**
-     * Drop the index for the specified element class ({@link Vertex} or {@link Edge}) and key.
-     *
-     * @param key          the property key to stop indexing
-     * @param elementClass the element class of the index to drop
-     * @param <E>          The type of the element class
-     */
-    public <E extends Element> void dropIndex(final String key, final Class<E> elementClass) {
-        if (Vertex.class.isAssignableFrom(elementClass)) {
-            if (null != this.vertexIndex) this.vertexIndex.dropKeyIndex(key);
-        } else if (Edge.class.isAssignableFrom(elementClass)) {
-            if (null != this.edgeIndex) this.edgeIndex.dropKeyIndex(key);
-        } else {
-            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
-        }
-    }
-
     /**
      * Return all the keys currently being index for said element class  ({@link Vertex} or {@link Edge}).
      *
@@ -548,10 +377,11 @@ public final class TinkerGraph implements Graph {
         }
     }
 
+    ///////////// Id manager ///////////////
     /**
-     * Construct an {@link TinkerGraph.IdManager} from the TinkerGraph {@code Configuration}.
+     * Construct an {@link IdManager} from the TinkerGraph {@code Configuration}.
      */
-    private static IdManager<?> selectIdManager(final Configuration config, final String configKey, final Class<? extends Element> clazz) {
+    protected static <T extends Element> IdManager<T> selectIdManager(final Configuration config, final String configKey, final Class<T> clazz) {
         final String vertexIdManagerConfigValue = config.getString(configKey, DefaultIdManager.ANY.name());
         try {
             return DefaultIdManager.valueOf(vertexIdManagerConfigValue);
@@ -564,15 +394,6 @@ public final class TinkerGraph implements Graph {
         }
     }
 
-    private TinkerServiceFactory instantiate(final String className) {
-        try {
-            return (TinkerServiceFactory) Class.forName(className).getConstructor(TinkerGraph.class).newInstance(this);
-        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
-                 NoSuchMethodException | InvocationTargetException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
     /**
      * TinkerGraph will use an implementation of this interface to generate identifiers when a user does not supply
      * them and to handle identifier conversions when querying to provide better flexibility with respect to
@@ -586,7 +407,7 @@ public final class TinkerGraph implements Graph {
         /**
          * Generate an identifier which should be unique to the {@link TinkerGraph} instance.
          */
-        T getNextId(final TinkerGraph graph);
+        T getNextId(final AbstractTinkerGraph graph);
 
         /**
          * Convert an identifier to the type required by the manager.
@@ -609,8 +430,8 @@ public final class TinkerGraph implements Graph {
          */
         LONG {
             @Override
-            public Long getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
+            public Long getNextId(final AbstractTinkerGraph graph) {
+                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.hasVertex(id) && !graph.hasEdge(id)).findAny().get();
             }
 
             @Override
@@ -644,8 +465,8 @@ public final class TinkerGraph implements Graph {
          */
         INTEGER {
             @Override
-            public Integer getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).map(Long::intValue).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
+            public Integer getNextId(final AbstractTinkerGraph graph) {
+                return Stream.generate(() -> (graph.currentId.incrementAndGet())).map(Long::intValue).filter(id -> !graph.hasVertex(id) && !graph.hasEdge(id)).findAny().get();
             }
 
             @Override
@@ -679,7 +500,7 @@ public final class TinkerGraph implements Graph {
          */
         UUID {
             @Override
-            public UUID getNextId(final TinkerGraph graph) {
+            public java.util.UUID getNextId(final AbstractTinkerGraph graph) {
                 return java.util.UUID.randomUUID();
             }
 
@@ -713,8 +534,8 @@ public final class TinkerGraph implements Graph {
          */
         ANY {
             @Override
-            public Long getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
+            public Long getNextId(final AbstractTinkerGraph graph) {
+                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.hasVertex(id) && !graph.hasEdge(id)).findAny().get();
             }
 
             @Override
@@ -732,4 +553,13 @@ public final class TinkerGraph implements Graph {
             return String.format("Expected an id that is convertible to %s but received %s - [%s]", expectedType, id.getClass(), id);
         }
     }
+
+    protected TinkerServiceRegistry.TinkerServiceFactory instantiate(final String className) {
+        try {
+            return (TinkerServiceRegistry.TinkerServiceFactory) Class.forName(className).getConstructor(AbstractTinkerGraph.class).newInstance(this);
+        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
+                 NoSuchMethodException | InvocationTargetException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerIndex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerIndex.java
new file mode 100644
index 0000000000..b65e90b7eb
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerIndex.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.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.structure.Element;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Storage for indexes that can be used by different implementations of {@link AbstractTinkerGraph}.
+ *
+ * @param <T> type of {@link Element} to be indexed
+ *
+ * @author Valentyn Kahamlyk
+ */
+public abstract class AbstractTinkerIndex<T extends Element> {
+
+    protected final Class<T> indexClass;
+    protected final AbstractTinkerGraph graph;
+    protected final Set<String> indexedKeys = new HashSet<>();
+
+    protected AbstractTinkerIndex(final AbstractTinkerGraph graph, final Class<T> indexClass) {
+        this.graph = graph;
+        this.indexClass = indexClass;
+    }
+
+    /**
+     * Get list of elements which have a property with the desired value.
+     * @param key property key
+     * @param value property value
+     * @return list of elements
+     */
+    public abstract List<T> get(final String key, final Object value);
+
+    /**
+     * Get count of elements which have a property with the desired value.
+     * @param key property key
+     * @param value property value
+     * @return count of elements
+     */
+    public abstract long count(final String key, final Object value);
+
+    /**
+     * Remove elements with some property from index.
+     * Convenient to use when removed only one property of an element.
+     * @param key property key
+     * @param value property value
+     * @param element changed element
+     */
+    public abstract void remove(final String key, final Object value, final T element);
+
+    /**
+     * Remove elements from all indexes.
+     * Can be used when element was deleted or has changed more than 1 property.
+     * @param element changed element
+     */
+    public abstract void removeElement(final T element);
+
+    /**
+     * Update index for single property of element.
+     * @param key property key
+     * @param newValue updated value of property
+     * @param oldValue optional value of property before update
+     * @param element changed element
+     */
+    public abstract void autoUpdate(final String key, final Object newValue, final Object oldValue, final T element);
+
+    /**
+     * Create new index
+     * @param key property key
+     */
+    public abstract void createKeyIndex(final String key);
+
+    /**
+     * Drop index
+     * @param key property key
+     */
+    public abstract void dropKeyIndex(final String key);
+
+    /**
+     * Get all index keys for Graph
+     * @return set of index keys
+     */
+    public Set<String> getIndexedKeys() {
+        return indexedKeys;
+    }
+
+    /**
+     * Provides a way for an index to have a {@code null} value as {@code ConcurrentHashMap} will not allow a
+     * {@code null} key.
+     */
+    public static Object indexable(final Object obj) {
+        return null == obj ? IndexedNull.instance() : obj;
+    }
+
+    public static final class IndexedNull {
+        private static final IndexedNull inst = new IndexedNull();
+
+        private IndexedNull() {}
+
+        static IndexedNull instance() {
+            return inst;
+        }
+
+        @Override
+        public int hashCode() {
+            return 751912123;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            return o instanceof IndexedNull;
+        }
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
index 02f7edea76..93dcf594d7 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
@@ -18,21 +18,21 @@
  */
 package org.apache.tinkerpop.gremlin.tinkergraph.structure;
 
-import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 /**
@@ -41,20 +41,44 @@ import java.util.stream.Collectors;
 public final class TinkerEdge extends TinkerElement implements Edge {
 
     protected Map<String, Property> properties;
-    protected final Vertex inVertex;
-    protected final Vertex outVertex;
+
+    protected Vertex inVertex = null;
+    protected Object inVertexId = null;
+    protected Vertex outVertex = null;
+    protected Object outVertexId= null;
+    private final AbstractTinkerGraph graph;
     private final boolean allowNullPropertyValues;
+    private final boolean isTxMode;
 
     protected TinkerEdge(final Object id, final Vertex outVertex, final String label, final Vertex inVertex) {
-        super(id, label);
-        this.outVertex = outVertex;
-        this.inVertex = inVertex;
-        this.allowNullPropertyValues = outVertex.graph().features().edge().supportsNullPropertyValues();
-        TinkerHelper.autoUpdateIndex(this, T.label.getAccessor(), this.label, null);
+       this(id, outVertex, label, inVertex, 0);
+    }
+
+    protected TinkerEdge(final Object id, final Vertex outVertex, final String label, final Vertex inVertex, final long currentVersion) {
+        this(id, (AbstractTinkerGraph) outVertex.graph(), outVertex.id(), label, inVertex.id(), currentVersion, false);
+        if (!isTxMode) {
+            this.inVertex = inVertex;
+            this.outVertex = outVertex;
+        }
+    }
+
+    private TinkerEdge(final Object id, AbstractTinkerGraph graph, final Object outVertexId, final String label, final Object inVertexId, final long currentVersion, final Boolean skipIndexUpdate) {
+        super(id, label, currentVersion);
+        isTxMode = graph instanceof TinkerTransactionGraph;
+        this.graph = graph;
+        if (isTxMode) {
+            this.outVertexId = outVertexId;
+            this.inVertexId = inVertexId;
+        }
+        this.allowNullPropertyValues = graph.features().edge().supportsNullPropertyValues();
+        if (!skipIndexUpdate)
+            TinkerIndexHelper.autoUpdateIndex(this, T.label.getAccessor(), this.label, null);
     }
 
     @Override
     public <V> Property<V> property(final String key, final V value) {
+        graph.touch(this);
+
         if (this.removed) throw elementAlreadyRemoved(Edge.class, id);
         ElementHelper.validateProperty(key, value);
 
@@ -65,11 +89,10 @@ public final class TinkerEdge extends TinkerElement implements Edge {
 
         final Property oldProperty = super.property(key);
         final Property<V> newProperty = new TinkerProperty<>(this, key, value);
-        if (null == this.properties) this.properties = new HashMap<>();
+        if (null == this.properties) this.properties = new ConcurrentHashMap<>();
         this.properties.put(key, newProperty);
-        TinkerHelper.autoUpdateIndex(this, key, value, oldProperty.isPresent() ? oldProperty.value() : null);
+        TinkerIndexHelper.autoUpdateIndex(this, key, value, oldProperty.isPresent() ? oldProperty.value() : null);
         return newProperty;
-
     }
 
     @Override
@@ -84,22 +107,9 @@ public final class TinkerEdge extends TinkerElement implements Edge {
 
     @Override
     public void remove() {
-        final TinkerVertex outVertex = (TinkerVertex) this.outVertex;
-        final TinkerVertex inVertex = (TinkerVertex) this.inVertex;
-
-        if (null != outVertex && null != outVertex.outEdges) {
-            final Set<Edge> edges = outVertex.outEdges.get(this.label());
-            if (null != edges)
-                edges.remove(this);
-        }
-        if (null != inVertex && null != inVertex.inEdges) {
-            final Set<Edge> edges = inVertex.inEdges.get(this.label());
-            if (null != edges)
-                edges.remove(this);
-        }
-
-        TinkerHelper.removeElementIndex(this);
-        ((TinkerGraph) this.graph()).edges.remove(this.id());
+        graph.touch(this);
+        TinkerIndexHelper.removeElementIndex(this);
+        graph.removeEdge(this.id());
         this.properties = null;
         this.removed = true;
     }
@@ -107,17 +117,37 @@ public final class TinkerEdge extends TinkerElement implements Edge {
     @Override
     public String toString() {
         return StringFactory.edgeString(this);
+    }
+
+    @Override
+    public Object clone() {
+        if (!isTxMode) {
+            // shallow copy for non-tx mode
+            final TinkerEdge edge = new TinkerEdge(id, outVertex, label, inVertex, currentVersion);
+            edge.properties = properties;
+            return edge;
+        }
+
+        final TinkerEdge edge = new TinkerEdge(id, graph, outVertexId, label, inVertexId, currentVersion, true);
+
+        if (properties != null) {
+            final Map<String, Property> cloned = new ConcurrentHashMap<>(properties.size());
+            properties.entrySet().stream().forEach(p -> cloned.put(p.getKey(), ((TinkerProperty) p.getValue()).copy(edge)));
+
+            edge.properties = cloned;
+        }
 
+        return edge;
     }
 
     @Override
     public Vertex outVertex() {
-        return this.outVertex;
+        return isTxMode ? graph.vertex(outVertexId) : outVertex;
     }
 
     @Override
     public Vertex inVertex() {
-        return this.inVertex;
+        return isTxMode ? graph.vertex(inVertexId) : inVertex;
     }
 
     @Override
@@ -125,23 +155,25 @@ public final class TinkerEdge extends TinkerElement implements Edge {
         if (removed) return Collections.emptyIterator();
         switch (direction) {
             case OUT:
-                return IteratorUtils.of(this.outVertex);
+                return IteratorUtils.of(this.outVertex());
             case IN:
-                return IteratorUtils.of(this.inVertex);
+                return IteratorUtils.of(this.inVertex());
             default:
-                return IteratorUtils.of(this.outVertex, this.inVertex);
+                return IteratorUtils.of(this.outVertex(), this.inVertex());
         }
     }
 
     @Override
     public Graph graph() {
-        return this.inVertex.graph();
+        return this.graph;
     }
 
     @Override
     public <V> Iterator<Property<V>> properties(final String... propertyKeys) {
         if (null == this.properties) return Collections.emptyIterator();
         if (propertyKeys.length == 1) {
+            if (null == propertyKeys[0])
+                return Collections.emptyIterator();
             final Property<V> property = this.properties.get(propertyKeys[0]);
             return null == property ? Collections.emptyIterator() : IteratorUtils.of(property);
         } else
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElement.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElement.java
index a2ee75b58f..81dea31f06 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElement.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElement.java
@@ -29,10 +29,18 @@ public abstract class TinkerElement implements Element {
     protected final Object id;
     protected final String label;
     protected boolean removed = false;
+    protected long currentVersion;
 
     protected TinkerElement(final Object id, final String label) {
         this.id = id;
         this.label = label;
+        currentVersion = -1;
+    }
+
+    protected TinkerElement(final Object id, final String label, final long currentVersion) {
+        this.id = id;
+        this.label = label;
+        this.currentVersion = currentVersion;
     }
 
     @Override
@@ -40,6 +48,8 @@ public abstract class TinkerElement implements Element {
         return ElementHelper.hashCode(this);
     }
 
+    public abstract  Object clone();
+
     @Override
     public Object id() {
         return this.id;
@@ -56,6 +66,8 @@ public abstract class TinkerElement implements Element {
         return ElementHelper.areEqual(this, object);
     }
 
+    public long version() { return this.currentVersion; }
+
     protected static IllegalStateException elementAlreadyRemoved(final Class<? extends Element> clazz, final Object id) {
         return new IllegalStateException(String.format("%s with id %s was removed.", clazz.getSimpleName(), id));
     }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElementContainer.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElementContainer.java
new file mode 100644
index 0000000000..277d48d3fe
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerElementContainer.java
@@ -0,0 +1,245 @@
+/*
+ * 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.tinkergraph.structure;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Container to store the value of an element which can be specific to each transaction.
+ * Responsible for transactional operations for the element that stores.
+ * @param <T> type of element to store.
+ */
+final class TinkerElementContainer<T extends TinkerElement> {
+    /**
+     * Committed value of element.
+     */
+    private T element;
+    /**
+     * Id of element. Used if element is removed or set to {@code null}.
+     */
+    private Object elementId;
+    /**
+     * Used to separate deleted elements from {@code null} ones.
+     */
+    private boolean isDeleted = false;
+    /**
+     * Value of elements updated in current transaction.
+     */
+    private ThreadLocal<T> transactionUpdatedValue = ThreadLocal.withInitial(() -> null);
+    /**
+     * Marker for element deleted in current transaction.
+     */
+    private ThreadLocal<Boolean> isDeletedInTx = ThreadLocal.withInitial(() -> false);
+
+    /**
+     * Marker for element modified in current transaction.
+     */
+    private ThreadLocal<Boolean> isModifiedInTx = ThreadLocal.withInitial(() -> false);
+
+    /**
+     * Count of usages of container in different transactions.
+     * Needed to understand whether this element is used in other transactions or it can be deleted during rollback.
+     */
+    private AtomicInteger usesInTransactions = new AtomicInteger(0);
+
+    /**
+     * Used to protect container from simultaneous modification in different transactions.
+     */
+    private final ReentrantLock lock = new ReentrantLock();
+
+    /**
+     * Constructor only requires the element id to be stored.
+     * @param elementId id of element to store.
+     */
+    public TinkerElementContainer(final Object elementId) {
+        this.elementId = elementId;
+    }
+
+    /**
+     * Get transaction specific value of stored element.
+     */
+    public T get() {
+        if (isDeletedInTx.get()) return null;
+        if (transactionUpdatedValue.get() != null) return transactionUpdatedValue.get();
+        if (isDeleted) return null;
+        return element;
+    }
+
+    public T getWithClone() {
+        if (isDeletedInTx.get()) return null;
+        if (transactionUpdatedValue.get() != null) return transactionUpdatedValue.get();
+        if (isDeleted || null == element) return null;
+
+        final T cloned = (T) element.clone();
+        transactionUpdatedValue.set(cloned);
+        return cloned;
+    }
+
+    /**
+     * Get current committed value of stored element.
+     */
+    public T getUnmodified() {
+        return element;
+    }
+
+    /**
+     * Get modified in the current transaction value of stored element.
+     */
+    public T getModified() {
+        return transactionUpdatedValue.get();
+    }
+
+    /**
+     * Get id of stored element.
+     */
+    public Object getElementId() {
+        return elementId;
+    }
+
+    /**
+     * Needed to understand if the element has changed in the current transaction
+     */
+    public boolean isChanged() {
+        return isDeletedInTx.get() || isModifiedInTx.get() && transactionUpdatedValue.get() != null;
+    }
+
+    /**
+     * Used to understand if the element has deleted in the current transaction
+     */
+    public boolean isDeleted() { return isDeleted || isDeletedInTx.get(); }
+
+    /**
+     * Mark element as deleted in the current transaction.
+     */
+    public void markDeleted(final TinkerTransaction tx) {
+        if (!isDeletedInTx.get()) {
+            usesInTransactions.incrementAndGet();
+            isDeletedInTx.set(true);
+            tx.markChanged(this);
+        }
+    }
+
+    /**
+     * Mark element as changed in the current transaction.
+     * A copy of the element is made and set as a value in the transaction.
+     * @param transactionElement updated element
+     * @param tx current transaction
+     */
+    public void touch(final T transactionElement, final TinkerTransaction tx) {
+        elementId = transactionElement.id();
+        if (transactionUpdatedValue.get() == transactionElement && isModifiedInTx.get()) return;
+
+        setDraft(transactionElement, tx);
+    }
+
+    /**
+     * Set element value specific to current transaction.
+     * @param transactionElement updated element
+     * @param tx current transaction
+     */
+    public void setDraft(final T transactionElement, final TinkerTransaction tx) {
+        elementId = transactionElement.id();
+        if (!isModifiedInTx.get())
+            usesInTransactions.incrementAndGet();
+        transactionUpdatedValue.set(transactionElement);
+        isModifiedInTx.set(true);
+        tx.markChanged(this);
+    }
+
+    /**
+     * Used to understand if elements was changed by other transaction.
+     */
+    public boolean updatedOutsideTransaction() {
+        // todo: do we need to check version on delete?
+        final T updatedValue = transactionUpdatedValue.get();
+        return isDeleted ||
+                element != null && updatedValue != null && updatedValue.version() != element.version();
+    }
+
+    /**
+     * Commit changes for the stored element.
+     * @param txVersion version of transaction
+     */
+    public void commit(final long txVersion) {
+        updateUsesCount();
+        if (isDeletedInTx.get()) {
+            // created and deleted in same tx
+            if (null != element)
+                element.removed = true;
+            element = null;
+            isDeleted = true;
+        } else {
+            element = transactionUpdatedValue.get();
+            element.currentVersion = txVersion;
+        }
+        reset();
+    }
+
+    /**
+     * Rollback changes for the stored element.
+     */
+    public void rollback() {
+        updateUsesCount();
+        reset();
+    }
+
+    /**
+     * After the transaction is completed, need to reduce the usage counters to be able to delete the container.
+     */
+    private void updateUsesCount() {
+        if (isDeletedInTx.get())
+            usesInTransactions.decrementAndGet();
+        if (isModifiedInTx.get())
+            usesInTransactions.decrementAndGet();
+    }
+
+    /**
+     * Used to check if container can be removed or still used by another transaction.
+     * Should be used after commit or rollback.
+     */
+    public boolean canBeRemoved() {
+        return usesInTransactions.get() == 0 && (isDeleted || element == null);
+    }
+
+    /**
+     * Cleanup changes made in the current transaction.
+     */
+    private void reset() {
+        transactionUpdatedValue.remove();
+        isDeletedInTx.set(false);
+        isModifiedInTx.set(false);
+    }
+
+    /**
+     * Try to lock container to apply changes to stored element.
+     * @return True if lock was successful.
+     */
+    public boolean tryLock() {
+        return lock.tryLock();
+    }
+
+    /**
+     * Release lock after applying changes.
+     */
+    public void releaseLock() {
+        if (lock.isHeldByCurrentThread())
+            lock.unlock();
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java
index a824603843..97ab12dc69 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java
@@ -25,15 +25,12 @@ import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 
-import java.io.File;
 import java.io.InputStream;
-import java.util.Arrays;
-import java.util.List;
 
 import static org.apache.tinkerpop.gremlin.structure.io.IoCore.gryo;
-import static org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph.DefaultIdManager;
-import static org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph.DefaultIdManager.INTEGER;
-import static org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph.DefaultIdManager.LONG;
+import static org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph.DefaultIdManager;
+import static org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph.DefaultIdManager.INTEGER;
+import static org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph.DefaultIdManager.LONG;
 
 /**
  * Helps create a variety of different toy graphs for testing and learning purposes.
@@ -54,10 +51,21 @@ public final class TinkerFactory {
         return g;
     }
 
+    /**
+     * Create the "classic" graph with transaction support.
+     * @see #createClassic()
+     */
+    public static TinkerTransactionGraph createTxClassic() {
+        final TinkerTransactionGraph g = getTinkerTransactionGraphWithClassicNumberManager();
+        generateClassic(g);
+        g.tx().commit();
+        return g;
+    }
+
     /**
      * Generate the graph in {@link #createClassic()} into an existing graph.
      */
-    public static void generateClassic(final TinkerGraph g) {
+    public static void generateClassic(final AbstractTinkerGraph g) {
         final Vertex marko = g.addVertex(T.id, 1, "name", "marko", "age", 29);
         final Vertex vadas = g.addVertex(T.id, 2, "name", "vadas", "age", 27);
         final Vertex lop = g.addVertex(T.id, 3, "name", "lop", "lang", "java");
@@ -82,10 +90,21 @@ public final class TinkerFactory {
         return g;
     }
 
+    /**
+     * Create the "modern" graph with transaction support.
+     * @see #createModern()
+     */
+    public static TinkerTransactionGraph createTxModern() {
+        final TinkerTransactionGraph g = getTinkerTransactionGraphWithCurrentNumberManager();
+        generateModern(g);
+        g.tx().commit();
+        return g;
+    }
+
     /**
      * Generate the graph in {@link #createModern()} into an existing graph.
      */
-    public static void generateModern(final TinkerGraph g) {
+    public static void generateModern(final AbstractTinkerGraph g) {
         final Vertex marko = g.addVertex(T.id, 1, T.label, "person");
         marko.property("name", "marko", T.id, 0l);
         marko.property("age", 29, T.id, 1l);
@@ -125,10 +144,23 @@ public final class TinkerFactory {
         return g;
     }
 
+    /**
+     * Create the "the crew" graph with transaction support.
+     * @see #createTheCrew()
+     */
+    public static TinkerTransactionGraph createTxTheCrew() {
+        final Configuration conf = getConfigurationWithCurrentNumberManager();
+        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.list.name());
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open(conf);
+        generateTheCrew(g);
+        g.tx().commit();
+        return g;
+    }
+
     /**
      * Generate the graph in {@link #createTheCrew()} into an existing graph.
      */
-    public static void generateTheCrew(final TinkerGraph g) {
+    public static void generateTheCrew(final AbstractTinkerGraph g) {
         final Vertex marko = g.addVertex(T.id, 1, T.label, "person", "name", "marko");
         final Vertex stephen = g.addVertex(T.id, 7, T.label, "person", "name", "stephen");
         final Vertex matthias = g.addVertex(T.id, 8, T.label, "person", "name", "matthias");
@@ -188,10 +220,21 @@ public final class TinkerFactory {
         return g;
     }
 
+    /**
+     * Creates the "kitchen sink" graph with transaction support.
+     * @see #createKitchenSink()
+     */
+    public static TinkerTransactionGraph createTxKitchenSink() {
+        final TinkerTransactionGraph g = getTinkerTransactionGraphWithCurrentNumberManager();
+        generateKitchenSink(g);
+        g.tx().commit();
+        return g;
+    }
+
     /**
      * Generate the graph in {@link #createKitchenSink()} into an existing graph.
      */
-    public static void generateKitchenSink(final TinkerGraph graph) {
+    public static void generateKitchenSink(final AbstractTinkerGraph graph) {
         final GraphTraversalSource g = graph.traversal();
         g.addV("loops").property(T.id, 1000).property("name", "loop").as("me").
           addE("self").to("me").property(T.id, 1001).
@@ -212,10 +255,21 @@ public final class TinkerFactory {
         return g;
     }
 
+    /**
+     * Creates the "grateful dead" graph with transaction support.
+     * @see #createGratefulDead()
+     */
+    public static TinkerTransactionGraph createTxGratefulDead() {
+        final TinkerTransactionGraph g = getTinkerTransactionGraphWithCurrentNumberManager();
+        generateGratefulDead(g);
+        g.tx().commit();
+        return g;
+    }
+
     /**
      * Generate the graph in {@link #createGratefulDead()} into an existing graph.
      */
-    public static void generateGratefulDead(final TinkerGraph graph) {
+    public static void generateGratefulDead(final AbstractTinkerGraph graph) {
         final InputStream stream = TinkerFactory.class.getResourceAsStream("grateful-dead.kryo");
         try {
             graph.io(gryo()).reader().create().readGraph(stream, graph);
@@ -228,6 +282,10 @@ public final class TinkerFactory {
         return TinkerGraph.open(getConfigurationWithCurrentNumberManager());
     }
 
+    private static TinkerTransactionGraph getTinkerTransactionGraphWithCurrentNumberManager() {
+        return TinkerTransactionGraph.open(getConfigurationWithCurrentNumberManager());
+    }
+
     private static Configuration getConfigurationWithCurrentNumberManager() {
         return getNumberIdManagerConfiguration(INTEGER, INTEGER, LONG);
     }
@@ -236,6 +294,10 @@ public final class TinkerFactory {
         return TinkerGraph.open(getConfigurationHWithClassicNumberManager());
     }
 
+    private static TinkerTransactionGraph getTinkerTransactionGraphWithClassicNumberManager() {
+        return TinkerTransactionGraph.open(getConfigurationHWithClassicNumberManager());
+    }
+
     private static Configuration getConfigurationHWithClassicNumberManager() {
         return getNumberIdManagerConfiguration(INTEGER, INTEGER, INTEGER);
     }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index 6110ab785c..40e23253a9 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -20,7 +20,6 @@ package org.apache.tinkerpop.gremlin.tinkergraph.structure;
 
 import org.apache.commons.configuration2.BaseConfiguration;
 import org.apache.commons.configuration2.Configuration;
-import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Element;
@@ -28,35 +27,24 @@ import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Transaction;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
-import org.apache.tinkerpop.gremlin.structure.io.Io;
-import org.apache.tinkerpop.gremlin.structure.io.IoCore;
-import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
-import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoVersion;
 import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
-import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer;
-import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy;
 import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Stream;
-
-import static org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry.TinkerServiceFactory;
 
 /**
  * An in-memory (with optional persistence on calls to {@link #close()}), reference implementation of the property
@@ -71,7 +59,7 @@ import static org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceReg
 @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER)
 @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_STANDARD)
 @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_COMPUTER)
-public final class TinkerGraph implements Graph {
+public final class TinkerGraph extends AbstractTinkerGraph {
 
     static {
         TraversalStrategies.GlobalCache.registerStrategies(TinkerGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies(
@@ -83,38 +71,11 @@ public final class TinkerGraph implements Graph {
         this.setProperty(Graph.GRAPH, TinkerGraph.class.getName());
     }};
 
-    public static final String GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER = "gremlin.tinkergraph.vertexIdManager";
-    public static final String GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER = "gremlin.tinkergraph.edgeIdManager";
-    public static final String GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER = "gremlin.tinkergraph.vertexPropertyIdManager";
-    public static final String GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY = "gremlin.tinkergraph.defaultVertexPropertyCardinality";
-    public static final String GREMLIN_TINKERGRAPH_GRAPH_LOCATION = "gremlin.tinkergraph.graphLocation";
-    public static final String GREMLIN_TINKERGRAPH_GRAPH_FORMAT = "gremlin.tinkergraph.graphFormat";
-    public static final String GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES = "gremlin.tinkergraph.allowNullPropertyValues";
-    public static final String GREMLIN_TINKERGRAPH_SERVICE = "gremlin.tinkergraph.service";
-
     private final TinkerGraphFeatures features = new TinkerGraphFeatures();
 
-    protected AtomicLong currentId = new AtomicLong(-1L);
     protected Map<Object, Vertex> vertices = new ConcurrentHashMap<>();
     protected Map<Object, Edge> edges = new ConcurrentHashMap<>();
 
-    protected TinkerGraphVariables variables = null;
-    protected TinkerGraphComputerView graphComputerView = null;
-    protected TinkerIndex<TinkerVertex> vertexIndex = null;
-    protected TinkerIndex<TinkerEdge> edgeIndex = null;
-
-    protected IdManager<?> vertexIdManager;
-    protected IdManager<?> edgeIdManager;
-    protected IdManager<?> vertexPropertyIdManager;
-    protected final VertexProperty.Cardinality defaultVertexPropertyCardinality;
-    protected final boolean allowNullPropertyValues;
-
-    protected final TinkerServiceRegistry serviceRegistry;
-
-    private final Configuration configuration;
-    private final String graphLocation;
-    private final String graphFormat;
-
     /**
      * An empty private constructor that initializes {@link TinkerGraph}.
      */
@@ -186,66 +147,69 @@ public final class TinkerGraph implements Graph {
         }
 
         final Vertex vertex = new TinkerVertex(idValue, label, this);
+        ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, keyValues);
         this.vertices.put(vertex.id(), vertex);
 
-        ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, keyValues);
         return vertex;
     }
 
     @Override
-    public <C extends GraphComputer> C compute(final Class<C> graphComputerClass) {
-        if (!graphComputerClass.equals(TinkerGraphComputer.class))
-            throw Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(graphComputerClass);
-        return (C) new TinkerGraphComputer(this);
+    public void removeVertex(final Object vertexId)
+    {
+        this.vertices.remove(vertexId);
     }
 
     @Override
-    public GraphComputer compute() {
-        return new TinkerGraphComputer(this);
-    }
+    public Edge addEdge(final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues) {
+        ElementHelper.validateLabel(label);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
 
-    @Override
-    public Variables variables() {
-        if (null == this.variables)
-            this.variables = new TinkerGraphVariables();
-        return this.variables;
-    }
+        Object idValue = edgeIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
 
-    @Override
-    public <I extends Io> I io(final Io.Builder<I> builder) {
-        if (builder.requiresVersion(GryoVersion.V1_0) || builder.requiresVersion(GraphSONVersion.V1_0))
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV1.instance())).create();
-        else if (builder.requiresVersion(GraphSONVersion.V2_0))   // there is no gryo v2
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV2.instance())).create();
-        else
-            return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV3.instance())).create();
+        final Edge edge;
+        if (null != idValue) {
+            if (edges.containsKey(idValue))
+                throw Graph.Exceptions.edgeWithIdAlreadyExists(idValue);
+        } else {
+            idValue = edgeIdManager.getNextId(this);
+        }
+
+        edge = new TinkerEdge(idValue, outVertex, label, inVertex);
+        ElementHelper.attachProperties(edge, keyValues);
+        edges.put(edge.id(), edge);
+        addOutEdge(outVertex, label, edge);
+        addInEdge(inVertex, label, edge);
+        return edge;
     }
 
     @Override
-    public String toString() {
-        return StringFactory.graphString(this, "vertices:" + this.vertices.size() + " edges:" + this.edges.size());
+    public void removeEdge(final Object edgeId) {
+        final Edge edge = edges.get(edgeId);
+        // already removed?
+        if (null == edge) return;
+
+        final TinkerVertex outVertex = (TinkerVertex) edge.outVertex();
+        final TinkerVertex inVertex = (TinkerVertex) edge.inVertex();
+
+        if (null != outVertex && null != outVertex.outEdges) {
+            final Set<Edge> edges = outVertex.outEdges.get(edge.label());
+            if (null != edges)
+                edges.removeIf(e -> e.id() == edgeId);
+        }
+        if (null != inVertex && null != inVertex.inEdges) {
+            final Set<Edge> edges = inVertex.inEdges.get(edge.label());
+            if (null != edges)
+                edges.removeIf(e -> e.id() == edgeId);
+        }
+
+        this.edges.remove(edgeId);
     }
 
+    @Override
     public void clear() {
+        super.clear();
         this.vertices.clear();
         this.edges.clear();
-        this.variables = null;
-        this.currentId.set(-1L);
-        this.vertexIndex = null;
-        this.edgeIndex = null;
-        this.graphComputerView = null;
-    }
-
-    /**
-     * This method only has an effect if the {@link #GREMLIN_TINKERGRAPH_GRAPH_LOCATION} is set, in which case the
-     * data in the graph is persisted to that location. This method may be called multiple times and does not release
-     * resources.
-     */
-    @Override
-    public void close() {
-        if (graphLocation != null) saveGraph();
-        // shutdown services
-        serviceRegistry.close();
     }
 
     @Override
@@ -254,15 +218,27 @@ public final class TinkerGraph implements Graph {
     }
 
     @Override
-    public Configuration configuration() {
-        return configuration;
-    }
+    public int getVerticesCount() { return vertices.size(); }
+
+    @Override
+    public boolean hasVertex(Object id) { return vertices.containsKey(id); }
+
+    @Override
+    public int getEdgesCount() {  return edges.size(); }
+
+    @Override
+    public boolean hasEdge(Object id) { return edges.containsKey(id); }
 
     @Override
     public TinkerServiceRegistry getServiceRegistry() {
         return serviceRegistry;
     }
 
+    @Override
+    public Vertex vertex(final Object vertexId) {
+        return vertices.get(vertexIdManager.convert(vertexId));
+    }
+
     @Override
     public Iterator<Vertex> vertices(final Object... vertexIds) {
         return createElementIterator(Vertex.class, vertices, vertexIdManager, vertexIds);
@@ -273,52 +249,11 @@ public final class TinkerGraph implements Graph {
         return createElementIterator(Edge.class, edges, edgeIdManager, edgeIds);
     }
 
-    private void loadGraph() {
-        final File f = new File(graphLocation);
-        if (f.exists() && f.isFile()) {
-            try {
-                if (graphFormat.equals("graphml")) {
-                    io(IoCore.graphml()).readGraph(graphLocation);
-                } else if (graphFormat.equals("graphson")) {
-                    io(IoCore.graphson()).readGraph(graphLocation);
-                } else if (graphFormat.equals("gryo")) {
-                    io(IoCore.gryo()).readGraph(graphLocation);
-                } else {
-                    io(IoCore.createIoBuilder(graphFormat)).readGraph(graphLocation);
-                }
-            } catch (Exception ex) {
-                throw new RuntimeException(String.format("Could not load graph at %s with %s", graphLocation, graphFormat), ex);
-            }
-        }
+    @Override
+    public Edge edge(final Object edgeId) {
+        return edges.get(edgeIdManager.convert(edgeId));
     }
 
-    private void saveGraph() {
-        final File f = new File(graphLocation);
-        if (f.exists()) {
-            f.delete();
-        } else {
-            final File parent = f.getParentFile();
-
-            // the parent would be null in the case of an relative path if the graphLocation was simply: "f.gryo"
-            if (parent != null && !parent.exists()) {
-                parent.mkdirs();
-            }
-        }
-
-        try {
-            if (graphFormat.equals("graphml")) {
-                io(IoCore.graphml()).writeGraph(graphLocation);
-            } else if (graphFormat.equals("graphson")) {
-                io(IoCore.graphson()).writeGraph(graphLocation);
-            } else if (graphFormat.equals("gryo")) {
-                io(IoCore.gryo()).writeGraph(graphLocation);
-            } else {
-                io(IoCore.createIoBuilder(graphFormat)).writeGraph(graphLocation);
-            }
-        } catch (Exception ex) {
-            throw new RuntimeException(String.format("Could not save graph at %s with %s", graphLocation, graphFormat), ex);
-        }
-    }
 
     private <T extends Element> Iterator<T> createElementIterator(final Class<T> clazz, final Map<Object, T> elements,
                                                                   final IdManager idManager,
@@ -347,6 +282,28 @@ public final class TinkerGraph implements Graph {
                 iterator;
     }
 
+    @Override
+    protected void addOutEdge(final TinkerVertex vertex, final String label, final Edge edge) {
+        if (null == vertex.outEdges) vertex.outEdges = new HashMap<>();
+        Set<Edge> edges = vertex.outEdges.get(label);
+        if (null == edges) {
+            edges = new HashSet<>();
+            vertex.outEdges.put(label, edges);
+        }
+        edges.add(edge);
+    }
+
+    @Override
+    protected void addInEdge(final TinkerVertex vertex, final String label, final Edge edge) {
+        if (null == vertex.inEdges) vertex.inEdges = new HashMap<>();
+        Set<Edge> edges = vertex.inEdges.get(label);
+        if (null == edges) {
+            edges = new HashSet<>();
+            vertex.inEdges.put(label, edges);
+        }
+        edges.add(edge);
+    }
+
     /**
      * Return TinkerGraph feature set.
      * <p/>
@@ -389,60 +346,6 @@ public final class TinkerGraph implements Graph {
 
     }
 
-    public class TinkerGraphVertexFeatures implements Features.VertexFeatures {
-
-        private final TinkerGraphVertexPropertyFeatures vertexPropertyFeatures = new TinkerGraphVertexPropertyFeatures();
-
-        private TinkerGraphVertexFeatures() {
-        }
-
-        @Override
-        public boolean supportsNullPropertyValues() {
-            return allowNullPropertyValues;
-        }
-
-        @Override
-        public Features.VertexPropertyFeatures properties() {
-            return vertexPropertyFeatures;
-        }
-
-        @Override
-        public boolean supportsCustomIds() {
-            return false;
-        }
-
-        @Override
-        public boolean willAllowId(final Object id) {
-            return vertexIdManager.allow(id);
-        }
-
-        @Override
-        public VertexProperty.Cardinality getCardinality(final String key) {
-            return defaultVertexPropertyCardinality;
-        }
-    }
-
-    public class TinkerGraphEdgeFeatures implements Features.EdgeFeatures {
-
-        private TinkerGraphEdgeFeatures() {
-        }
-
-        @Override
-        public boolean supportsNullPropertyValues() {
-            return allowNullPropertyValues;
-        }
-
-        @Override
-        public boolean supportsCustomIds() {
-            return false;
-        }
-
-        @Override
-        public boolean willAllowId(final Object id) {
-            return edgeIdManager.allow(id);
-        }
-    }
-
     public class TinkerGraphGraphFeatures implements Features.GraphFeatures {
 
         private TinkerGraphGraphFeatures() {
@@ -470,26 +373,6 @@ public final class TinkerGraph implements Graph {
 
     }
 
-    public class TinkerGraphVertexPropertyFeatures implements Features.VertexPropertyFeatures {
-
-        private TinkerGraphVertexPropertyFeatures() {
-        }
-
-        @Override
-        public boolean supportsNullPropertyValues() {
-            return allowNullPropertyValues;
-        }
-
-        @Override
-        public boolean supportsCustomIds() {
-            return false;
-        }
-
-        @Override
-        public boolean willAllowId(final Object id) {
-            return vertexIdManager.allow(id);
-        }
-    }
 
     ///////////// GRAPH SPECIFIC INDEXING METHODS ///////////////
 
@@ -530,206 +413,4 @@ public final class TinkerGraph implements Graph {
             throw new IllegalArgumentException("Class is not indexable: " + elementClass);
         }
     }
-
-    /**
-     * Return all the keys currently being index for said element class  ({@link Vertex} or {@link Edge}).
-     *
-     * @param elementClass the element class to get the indexed keys for
-     * @param <E>          The type of the element class
-     * @return the set of keys currently being indexed
-     */
-    public <E extends Element> Set<String> getIndexedKeys(final Class<E> elementClass) {
-        if (Vertex.class.isAssignableFrom(elementClass)) {
-            return null == this.vertexIndex ? Collections.emptySet() : this.vertexIndex.getIndexedKeys();
-        } else if (Edge.class.isAssignableFrom(elementClass)) {
-            return null == this.edgeIndex ? Collections.emptySet() : this.edgeIndex.getIndexedKeys();
-        } else {
-            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
-        }
-    }
-
-    /**
-     * Construct an {@link TinkerGraph.IdManager} from the TinkerGraph {@code Configuration}.
-     */
-    private static IdManager<?> selectIdManager(final Configuration config, final String configKey, final Class<? extends Element> clazz) {
-        final String vertexIdManagerConfigValue = config.getString(configKey, DefaultIdManager.ANY.name());
-        try {
-            return DefaultIdManager.valueOf(vertexIdManagerConfigValue);
-        } catch (IllegalArgumentException iae) {
-            try {
-                return (IdManager) Class.forName(vertexIdManagerConfigValue).newInstance();
-            } catch (Exception ex) {
-                throw new IllegalStateException(String.format("Could not configure TinkerGraph %s id manager with %s", clazz.getSimpleName(), vertexIdManagerConfigValue));
-            }
-        }
-    }
-
-    private TinkerServiceFactory instantiate(final String className) {
-        try {
-            return (TinkerServiceFactory) Class.forName(className).getConstructor(TinkerGraph.class).newInstance(this);
-        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
-                 NoSuchMethodException | InvocationTargetException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    /**
-     * TinkerGraph will use an implementation of this interface to generate identifiers when a user does not supply
-     * them and to handle identifier conversions when querying to provide better flexibility with respect to
-     * handling different data types that mean the same thing.  For example, the
-     * {@link DefaultIdManager#LONG} implementation will allow {@code g.vertices(1l, 2l)} and
-     * {@code g.vertices(1, 2)} to both return values.
-     *
-     * @param <T> the id type
-     */
-    public interface IdManager<T> {
-        /**
-         * Generate an identifier which should be unique to the {@link TinkerGraph} instance.
-         */
-        T getNextId(final TinkerGraph graph);
-
-        /**
-         * Convert an identifier to the type required by the manager.
-         */
-        T convert(final Object id);
-
-        /**
-         * Determine if an identifier is allowed by this manager given its type.
-         */
-        boolean allow(final Object id);
-    }
-
-    /**
-     * A default set of {@link IdManager} implementations for common identifier types.
-     */
-    public enum DefaultIdManager implements IdManager {
-        /**
-         * Manages identifiers of type {@code Long}. Will convert any class that extends from {@link Number} to a
-         * {@link Long} and will also attempt to convert {@code String} values
-         */
-        LONG {
-            @Override
-            public Long getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
-            }
-
-            @Override
-            public Object convert(final Object id) {
-                if (null == id)
-                    return null;
-                else if (id instanceof Long)
-                    return id;
-                else if (id instanceof Number)
-                    return ((Number) id).longValue();
-                else if (id instanceof String) {
-                    try {
-                        return Long.parseLong((String) id);
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(createErrorMessage(Long.class, id));
-                    }
-                }
-                else
-                    throw new IllegalArgumentException(createErrorMessage(Long.class, id));
-            }
-
-            @Override
-            public boolean allow(final Object id) {
-                return id instanceof Number || id instanceof String;
-            }
-        },
-
-        /**
-         * Manages identifiers of type {@code Integer}. Will convert any class that extends from {@link Number} to a
-         * {@link Integer} and will also attempt to convert {@code String} values
-         */
-        INTEGER {
-            @Override
-            public Integer getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).map(Long::intValue).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
-            }
-
-            @Override
-            public Object convert(final Object id) {
-                if (null == id)
-                    return null;
-                else if (id instanceof Integer)
-                    return id;
-                else if (id instanceof Number)
-                    return ((Number) id).intValue();
-                else if (id instanceof String) {
-                    try {
-                        return Integer.parseInt((String) id);
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(createErrorMessage(Integer.class, id));
-                    }
-                }
-                else
-                    throw new IllegalArgumentException(createErrorMessage(Integer.class, id));
-            }
-
-            @Override
-            public boolean allow(final Object id) {
-                return id instanceof Number || id instanceof String;
-            }
-        },
-
-        /**
-         * Manages identifiers of type {@code UUID}. Will convert {@code String} values to
-         * {@code UUID}.
-         */
-        UUID {
-            @Override
-            public UUID getNextId(final TinkerGraph graph) {
-                return java.util.UUID.randomUUID();
-            }
-
-            @Override
-            public Object convert(final Object id) {
-                if (null == id)
-                    return null;
-                else if (id instanceof java.util.UUID)
-                    return id;
-                else  if (id instanceof String) {
-                    try {
-                        return java.util.UUID.fromString((String) id);
-                    } catch (IllegalArgumentException iae) {
-                        throw new IllegalArgumentException(createErrorMessage(java.util.UUID.class, id));
-                    }
-                } else
-                    throw new IllegalArgumentException(createErrorMessage(java.util.UUID.class, id));
-            }
-
-            @Override
-            public boolean allow(final Object id) {
-                return id instanceof UUID || id instanceof String;
-            }
-        },
-
-        /**
-         * Manages identifiers of any type.  This represents the default way {@link TinkerGraph} has always worked.
-         * In other words, there is no identifier conversion so if the identifier of a vertex is a {@code Long}, then
-         * trying to request it with an {@code Integer} will have no effect. Also, like the original
-         * {@link TinkerGraph}, it will generate {@link Long} values for identifiers.
-         */
-        ANY {
-            @Override
-            public Long getNextId(final TinkerGraph graph) {
-                return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get();
-            }
-
-            @Override
-            public Object convert(final Object id) {
-                return id;
-            }
-
-            @Override
-            public boolean allow(final Object id) {
-                return true;
-            }
-        };
-
-        private static String createErrorMessage(final Class<?> expectedType, final Object id) {
-            return String.format("Expected an id that is convertible to %s but received %s - [%s]", expectedType, id.getClass(), id);
-        }
-    }
 }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
index b0056af4b9..9614ff3051 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
@@ -23,17 +23,14 @@ import org.apache.tinkerpop.gremlin.process.computer.VertexComputeKey;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Element;
-import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Property;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
-import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -53,113 +50,26 @@ public final class TinkerHelper {
     private TinkerHelper() {
     }
 
-    protected static Edge addEdge(final TinkerGraph graph, final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues) {
-        ElementHelper.validateLabel(label);
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
-
-        Object idValue = graph.edgeIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
-
-        final Edge edge;
-        if (null != idValue) {
-            if (graph.edges.containsKey(idValue))
-                throw Graph.Exceptions.edgeWithIdAlreadyExists(idValue);
-        } else {
-            idValue = graph.edgeIdManager.getNextId(graph);
-        }
-
-        edge = new TinkerEdge(idValue, outVertex, label, inVertex);
-        ElementHelper.attachProperties(edge, keyValues);
-        graph.edges.put(edge.id(), edge);
-        TinkerHelper.addOutEdge(outVertex, label, edge);
-        TinkerHelper.addInEdge(inVertex, label, edge);
-        return edge;
-
-    }
-
-    protected static void addOutEdge(final TinkerVertex vertex, final String label, final Edge edge) {
-        if (null == vertex.outEdges) vertex.outEdges = new HashMap<>();
-        Set<Edge> edges = vertex.outEdges.get(label);
-        if (null == edges) {
-            edges = new HashSet<>();
-            vertex.outEdges.put(label, edges);
-        }
-        edges.add(edge);
-    }
-
-    protected static void addInEdge(final TinkerVertex vertex, final String label, final Edge edge) {
-        if (null == vertex.inEdges) vertex.inEdges = new HashMap<>();
-        Set<Edge> edges = vertex.inEdges.get(label);
-        if (null == edges) {
-            edges = new HashSet<>();
-            vertex.inEdges.put(label, edges);
-        }
-        edges.add(edge);
-    }
-
-    public static List<TinkerVertex> queryVertexIndex(final TinkerGraph graph, final String key, final Object value) {
-        return null == graph.vertexIndex ? Collections.emptyList() : graph.vertexIndex.get(key, value);
-    }
-
-    public static List<TinkerEdge> queryEdgeIndex(final TinkerGraph graph, final String key, final Object value) {
-        return null == graph.edgeIndex ? Collections.emptyList() : graph.edgeIndex.get(key, value);
+    public static Map<String, List<VertexProperty>> getProperties(final TinkerVertex vertex) {
+        return null == vertex.properties ? Collections.emptyMap() : vertex.properties;
     }
 
-    public static boolean inComputerMode(final TinkerGraph graph) {
+    public static boolean inComputerMode(final AbstractTinkerGraph graph) {
         return null != graph.graphComputerView;
     }
 
-    public static TinkerGraphComputerView createGraphComputerView(final TinkerGraph graph, final GraphFilter graphFilter, final Set<VertexComputeKey> computeKeys) {
+    public static TinkerGraphComputerView createGraphComputerView(final AbstractTinkerGraph graph, final GraphFilter graphFilter, final Set<VertexComputeKey> computeKeys) {
         return graph.graphComputerView = new TinkerGraphComputerView(graph, graphFilter, computeKeys);
     }
 
-    public static TinkerGraphComputerView getGraphComputerView(final TinkerGraph graph) {
+    public static TinkerGraphComputerView getGraphComputerView(final AbstractTinkerGraph graph) {
         return graph.graphComputerView;
     }
 
-    public static void dropGraphComputerView(final TinkerGraph graph) {
+    public static void dropGraphComputerView(final AbstractTinkerGraph graph) {
         graph.graphComputerView = null;
     }
 
-    public static Map<String, List<VertexProperty>> getProperties(final TinkerVertex vertex) {
-        return null == vertex.properties ? Collections.emptyMap() : vertex.properties;
-    }
-
-    public static void autoUpdateIndex(final TinkerEdge edge, final String key, final Object newValue, final Object oldValue) {
-        final TinkerGraph graph = (TinkerGraph) edge.graph();
-        if (graph.edgeIndex != null)
-            graph.edgeIndex.autoUpdate(key, newValue, oldValue, edge);
-    }
-
-    public static void autoUpdateIndex(final TinkerVertex vertex, final String key, final Object newValue, final Object oldValue) {
-        final TinkerGraph graph = (TinkerGraph) vertex.graph();
-        if (graph.vertexIndex != null)
-            graph.vertexIndex.autoUpdate(key, newValue, oldValue, vertex);
-    }
-
-    public static void removeElementIndex(final TinkerVertex vertex) {
-        final TinkerGraph graph = (TinkerGraph) vertex.graph();
-        if (graph.vertexIndex != null)
-            graph.vertexIndex.removeElement(vertex);
-    }
-
-    public static void removeElementIndex(final TinkerEdge edge) {
-        final TinkerGraph graph = (TinkerGraph) edge.graph();
-        if (graph.edgeIndex != null)
-            graph.edgeIndex.removeElement(edge);
-    }
-
-    public static void removeIndex(final TinkerVertex vertex, final String key, final Object value) {
-        final TinkerGraph graph = (TinkerGraph) vertex.graph();
-        if (graph.vertexIndex != null)
-            graph.vertexIndex.remove(key, value, vertex);
-    }
-
-    public static void removeIndex(final TinkerEdge edge, final String key, final Object value) {
-        final TinkerGraph graph = (TinkerGraph) edge.graph();
-        if (graph.edgeIndex != null)
-            graph.edgeIndex.remove(key, value, edge);
-    }
-
     public static Iterator<TinkerEdge> getEdges(final TinkerVertex vertex, final Direction direction, final String... edgeLabels) {
         final List<Edge> edges = new ArrayList<>();
         if (direction.equals(Direction.OUT) || direction.equals(Direction.BOTH)) {
@@ -185,6 +95,39 @@ public final class TinkerHelper {
         return (Iterator) edges.iterator();
     }
 
+    public static Iterator<TinkerEdge> getEdgesTx(final TinkerVertex vertex, final Direction direction, final String... edgeLabels) {
+        final List<Object> outEdgeIds = new ArrayList<>();
+        if (direction.equals(Direction.OUT) || direction.equals(Direction.BOTH)) {
+            if (vertex.outEdgesId != null) {
+                if (edgeLabels.length == 0)
+                    vertex.outEdgesId.values().forEach(outEdgeIds::addAll);
+                else if (edgeLabels.length == 1)
+                    outEdgeIds.addAll(vertex.outEdgesId.getOrDefault(edgeLabels[0], Collections.emptySet()));
+                else
+                    Stream.of(edgeLabels).map(vertex.outEdgesId::get).filter(Objects::nonNull).forEach(outEdgeIds::addAll);
+            }
+        }
+
+        final List<Object> inEdgeIds = new ArrayList<>();
+        if (direction.equals(Direction.IN) || direction.equals(Direction.BOTH)) {
+            if (vertex.inEdgesId != null) {
+                if (edgeLabels.length == 0)
+                    vertex.inEdgesId.values().forEach(inEdgeIds::addAll);
+                else if (edgeLabels.length == 1)
+                    inEdgeIds.addAll(vertex.inEdgesId.getOrDefault(edgeLabels[0], Collections.emptySet()));
+                else
+                    Stream.of(edgeLabels).map(vertex.inEdgesId::get).filter(Objects::nonNull).forEach(inEdgeIds::addAll);
+            }
+        }
+
+        return outEdgeIds.size() == 0 && inEdgeIds.size() == 0
+                ? Collections.emptyIterator()
+                : Stream.concat(outEdgeIds.stream(), inEdgeIds.stream())
+                    .map(id -> ((AbstractTinkerGraph) vertex.graph()).edge(id))
+                    .filter(v -> v != null)
+                    .map(v -> (TinkerEdge) v).iterator();
+    }
+
     public static Iterator<TinkerVertex> getVertices(final TinkerVertex vertex, final Direction direction, final String... edgeLabels) {
         final List<Vertex> vertices = new ArrayList<>();
         if (direction.equals(Direction.OUT) || direction.equals(Direction.BOTH)) {
@@ -210,19 +153,47 @@ public final class TinkerHelper {
         return (Iterator) vertices.iterator();
     }
 
-    public static Map<Object, Vertex> getVertices(final TinkerGraph graph) {
-        return graph.vertices;
-    }
+    public static Iterator<TinkerVertex> getVerticesTx(final TinkerVertex vertex, final Direction direction, final String... edgeLabels) {
+        final Set<Object> inEdgesIds = new HashSet<>();
+        if (direction.equals(Direction.OUT) || direction.equals(Direction.BOTH)) {
+            if (vertex.outEdgesId != null) {
+                if (edgeLabels.length == 0)
+                    vertex.outEdgesId.values().forEach(set -> set.forEach(edge -> inEdgesIds.add(edge)));
+                else if (edgeLabels.length == 1)
+                    vertex.outEdgesId.getOrDefault(edgeLabels[0], Collections.emptySet()).forEach(edge -> inEdgesIds.add(edge));
+                else
+                    Stream.of(edgeLabels).map(vertex.outEdgesId::get).filter(Objects::nonNull).flatMap(Set::stream).forEach(edge -> inEdgesIds.add(edge));
+            }
+        }
+        final Set<Object> outEdgesIds = new HashSet<>();
+        if (direction.equals(Direction.IN) || direction.equals(Direction.BOTH)) {
+            if (vertex.inEdgesId != null) {
+                if (edgeLabels.length == 0)
+                    vertex.inEdgesId.values().forEach(set -> set.forEach(edge -> outEdgesIds.add(edge)));
+                else if (edgeLabels.length == 1)
+                    vertex.inEdgesId.getOrDefault(edgeLabels[0], Collections.emptySet()).forEach(edge -> outEdgesIds.add(edge));
+                else
+                    Stream.of(edgeLabels).map(vertex.inEdgesId::get).filter(Objects::nonNull).flatMap(Set::stream).forEach(edge -> outEdgesIds.add(edge));
+            }
+        }
+
+        final List<Vertex> vertices = new ArrayList<>();
+        if (inEdgesIds.size() != 0)
+            vertex.graph().edges(inEdgesIds.toArray()).forEachRemaining(edge -> vertices.add(edge.inVertex()));
+        if (outEdgesIds.size() != 0)
+            vertex.graph().edges(outEdgesIds.toArray()).forEachRemaining(edge -> vertices.add(edge.outVertex()));
 
-    public static Map<Object, Edge> getEdges(final TinkerGraph graph) {
-        return graph.edges;
+        return vertices.size() == 0
+                ? Collections.emptyIterator()
+                : vertices.stream().map(v -> (TinkerVertex) v).iterator();
     }
 
+    // todo: move to SearchHelper?
     /**
      * Search for {@link Property}s attached to {@link Element}s of the supplied element type using the supplied
      * regex. This is a basic scan+filter operation, not a full text search against an index.
      */
-    public static <E extends Element> Iterator<Property> search(final TinkerGraph graph, final String regex,
+    public static <E extends Element> Iterator<Property> search(final AbstractTinkerGraph graph, final String regex,
                                                                 final Optional<Class<E>> type) {
 
         final Supplier<Iterator<Element>> vertices = () -> IteratorUtils.cast(graph.vertices());
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
index 75924dadd4..58504ef903 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
@@ -25,7 +25,6 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -34,16 +33,12 @@ import java.util.concurrent.ConcurrentHashMap;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-final class TinkerIndex<T extends Element> {
+final class TinkerIndex<T extends Element> extends AbstractTinkerIndex<T> {
 
     protected Map<String, Map<Object, Set<T>>> index = new ConcurrentHashMap<>();
-    protected final Class<T> indexClass;
-    private final Set<String> indexedKeys = new HashSet<>();
-    private final TinkerGraph graph;
 
     public TinkerIndex(final TinkerGraph graph, final Class<T> indexClass) {
-        this.graph = graph;
-        this.indexClass = indexClass;
+        super(graph, indexClass);
     }
 
     protected void put(final String key, final Object value, final T element) {
@@ -52,14 +47,16 @@ final class TinkerIndex<T extends Element> {
             this.index.putIfAbsent(key, new ConcurrentHashMap<>());
             keyMap = this.index.get(key);
         }
-        Set<T> objects = keyMap.get(value);
+        final Object indexableValue = indexable(value);
+        Set<T> objects = keyMap.get(indexableValue);
         if (null == objects) {
-            keyMap.putIfAbsent(value, ConcurrentHashMap.newKeySet());
-            objects = keyMap.get(value);
+            keyMap.putIfAbsent(indexableValue, ConcurrentHashMap.newKeySet());
+            objects = keyMap.get(indexableValue);
         }
         objects.add(element);
     }
 
+    @Override
     public List<T> get(final String key, final Object value) {
         final Map<Object, Set<T>> keyMap = this.index.get(key);
         if (null == keyMap) {
@@ -73,6 +70,7 @@ final class TinkerIndex<T extends Element> {
         }
     }
 
+    @Override
     public long count(final String key, final Object value) {
         final Map<Object, Set<T>> keyMap = this.index.get(key);
         if (null == keyMap) {
@@ -86,6 +84,7 @@ final class TinkerIndex<T extends Element> {
         }
     }
 
+    @Override
     public void remove(final String key, final Object value, final T element) {
         final Map<Object, Set<T>> keyMap = this.index.get(key);
         if (null != keyMap) {
@@ -99,6 +98,7 @@ final class TinkerIndex<T extends Element> {
         }
     }
 
+    @Override
     public void removeElement(final T element) {
         if (this.indexClass.isAssignableFrom(element.getClass())) {
             for (Map<Object, Set<T>> map : index.values()) {
@@ -109,6 +109,7 @@ final class TinkerIndex<T extends Element> {
         }
     }
 
+    @Override
     public void autoUpdate(final String key, final Object newValue, final Object oldValue, final T element) {
         if (this.indexedKeys.contains(key)) {
             this.remove(key, oldValue, element);
@@ -116,6 +117,7 @@ final class TinkerIndex<T extends Element> {
         }
     }
 
+    @Override
     public void createKeyIndex(final String key) {
         if (null == key)
             throw Graph.Exceptions.argumentCanNotBeNull("key");
@@ -127,49 +129,19 @@ final class TinkerIndex<T extends Element> {
         this.indexedKeys.add(key);
 
         (Vertex.class.isAssignableFrom(this.indexClass) ?
-                this.graph.vertices.values().parallelStream() :
-                this.graph.edges.values().parallelStream())
+                // cleaner to use graph.vertices(), but graph.vertices is quicker
+                ((TinkerGraph)this.graph).vertices.values().parallelStream() :
+                ((TinkerGraph)this.graph).edges.values().parallelStream())
                 .map(e -> new Object[]{((T) e).property(key), e})
                 .filter(a -> ((Property) a[0]).isPresent())
                 .forEach(a -> this.put(key, ((Property) a[0]).value(), (T) a[1]));
     }
 
+    @Override
     public void dropKeyIndex(final String key) {
         if (this.index.containsKey(key))
             this.index.remove(key).clear();
 
         this.indexedKeys.remove(key);
     }
-
-    /**
-     * Provides a way for an index to have a {@code null} value as {@code ConcurrentHashMap} will not allow a
-     * {@code null} key.
-     */
-    public static Object indexable(final Object obj) {
-        return null == obj ? IndexedNull.instance() : obj;
-    }
-
-    public Set<String> getIndexedKeys() {
-        return this.indexedKeys;
-    }
-
-    public static final class IndexedNull {
-        private static final IndexedNull inst = new IndexedNull();
-
-        private IndexedNull() {}
-
-        static IndexedNull instance() {
-            return inst;
-        }
-
-        @Override
-        public int hashCode() {
-            return 751912123;
-        }
-
-        @Override
-        public boolean equals(final Object o) {
-            return o instanceof IndexedNull;
-        }
-    }
 }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndexHelper.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndexHelper.java
new file mode 100644
index 0000000000..7d9f225427
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndexHelper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.tinkergraph.structure;
+
+import java.util.Collections;
+import java.util.List;
+
+public final class TinkerIndexHelper {
+
+    private TinkerIndexHelper() {
+    }
+
+    public static List<TinkerVertex> queryVertexIndex(final AbstractTinkerGraph graph, final String key, final Object value) {
+        return null == graph.vertexIndex ? Collections.emptyList() : graph.vertexIndex.get(key, value);
+    }
+
+    public static List<TinkerEdge> queryEdgeIndex(final AbstractTinkerGraph graph, final String key, final Object value) {
+        return null == graph.edgeIndex ? Collections.emptyList() : graph.edgeIndex.get(key, value);
+    }
+
+    public static void autoUpdateIndex(final TinkerEdge edge, final String key, final Object newValue, final Object oldValue) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) edge.graph();
+        if (graph.edgeIndex != null)
+            graph.edgeIndex.autoUpdate(key, newValue, oldValue, edge);
+    }
+
+    public static void autoUpdateIndex(final TinkerVertex vertex, final String key, final Object newValue, final Object oldValue) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) vertex.graph();
+        if (graph.vertexIndex != null)
+            graph.vertexIndex.autoUpdate(key, newValue, oldValue, vertex);
+    }
+
+    public static void removeElementIndex(final TinkerVertex vertex) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) vertex.graph();
+        if (graph.vertexIndex != null)
+            graph.vertexIndex.removeElement(vertex);
+    }
+
+    public static void removeElementIndex(final TinkerEdge edge) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) edge.graph();
+        if (graph.edgeIndex != null)
+            graph.edgeIndex.removeElement(edge);
+    }
+
+    public static void removeIndex(final TinkerVertex vertex, final String key, final Object value) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) vertex.graph();
+        if (graph.vertexIndex != null)
+            graph.vertexIndex.remove(key, value, vertex);
+    }
+
+    public static void removeIndex(final TinkerEdge edge, final String key, final Object value) {
+        final AbstractTinkerGraph graph = (AbstractTinkerGraph) edge.graph();
+        if (graph.edgeIndex != null)
+            graph.edgeIndex.remove(key, value, edge);
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
index db40346fa0..f80f4ff8ba 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
@@ -80,10 +80,22 @@ public final class TinkerProperty<V> implements Property<V> {
     @Override
     public void remove() {
         if (this.element instanceof Edge) {
+            ((AbstractTinkerGraph) this.element.graph()).touch((TinkerEdge) this.element);
             ((TinkerEdge) this.element).properties.remove(this.key);
-            TinkerHelper.removeIndex((TinkerEdge) this.element, this.key, this.value);
+            TinkerIndexHelper.removeIndex((TinkerEdge) this.element, this.key, this.value);
         } else {
+            final TinkerVertex vertex = (TinkerVertex) ((TinkerVertexProperty) this.element).element();
+            ((AbstractTinkerGraph) vertex.graph()).touch(vertex);
             ((TinkerVertexProperty) this.element).properties.remove(this.key);
         }
     }
+
+    @Override
+    public Object clone() {
+        return new TinkerProperty(element, key, value);
+    }
+
+    public TinkerProperty copy(final Element newOwner) {
+        return new TinkerProperty(newOwner, key, value);
+    }
 }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransaction.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransaction.java
new file mode 100644
index 0000000000..c5eec18052
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransaction.java
@@ -0,0 +1,232 @@
+/*
+ * 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.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Transaction;
+import org.apache.tinkerpop.gremlin.structure.util.AbstractThreadLocalTransaction;
+import org.apache.tinkerpop.gremlin.structure.util.TransactionException;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Implementation of {@link AbstractThreadLocalTransaction} for {@link TinkerTransactionGraph}
+ */
+final class TinkerTransaction extends AbstractThreadLocalTransaction {
+
+    private static final String TX_CONFLICT = "Conflict: element modified in another transaction";
+
+    /**
+     * Initial value of transaction number.
+     */
+    private static final long NOT_STARTED = -1;
+
+    /**
+     * Counter for opened transactions. Used to get unique id for each new transaction.
+     */
+    private static final AtomicLong openedTx;
+
+    /**
+     * Unique number of transaction for each thread.
+     */
+    private final ThreadLocal<Long> txNumber = ThreadLocal.withInitial(() -> NOT_STARTED);
+
+    /**
+     * Set of references to vertex containers changed in current transaction.
+     */
+    private final ThreadLocal<Set<TinkerElementContainer<TinkerVertex>>> txChangedVertices = new ThreadLocal<>();
+
+    /**
+     * Set of references to edge containers changed in current transaction.
+     */
+    private final ThreadLocal<Set<TinkerElementContainer<TinkerEdge>>> txChangedEdges = new ThreadLocal<>();
+
+    private final TinkerTransactionGraph graph;
+
+    static {
+        openedTx = new AtomicLong(0);
+    }
+
+    public TinkerTransaction(final TinkerTransactionGraph g) {
+        super(g);
+        graph = g;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return txNumber.get() != NOT_STARTED;
+    }
+
+    @Override
+    public <T extends TraversalSource> T begin() {
+        doOpen();
+        return super.begin();
+    }
+
+    @Override
+    protected void doOpen() {
+        if (isOpen())
+            Transaction.Exceptions.transactionAlreadyOpen();
+
+        txNumber.set(openedTx.getAndIncrement());
+    }
+
+    protected long getTxNumber() {
+        if (!isOpen()) txNumber.set(openedTx.getAndIncrement());
+        return txNumber.get();
+    }
+
+    /**
+     * Adds element to list of changes in current transaction.
+     */
+    protected <T extends TinkerElement> void markChanged(TinkerElementContainer<T> container) {
+        if (!isOpen()) txNumber.set(openedTx.getAndIncrement());
+
+        T element = container.getUnmodified();
+        if (null == element) element = container.getModified();
+        if (element instanceof TinkerVertex) {
+            if (null == txChangedVertices.get())
+                txChangedVertices.set(new HashSet<>());
+            txChangedVertices.get().add((TinkerElementContainer<TinkerVertex>) container);
+        } else {
+            if (null == txChangedEdges.get())
+                txChangedEdges.set(new HashSet<>());
+            txChangedEdges.get().add((TinkerElementContainer<TinkerEdge>) container);
+        }
+    }
+
+    /**
+     * Try to commit all changes made in current transaction.
+     * Workflow:
+     * 1. collect all changes
+     * 2. verify if any elements already changed, throw {@link TransactionException} if any
+     * 3. try to lock all containers to prevent other tx from making changes
+     * 4. one more time verify elements versions
+     * 5. update indices
+     * 6. commit all changes
+     * On {@link TransactionException}:
+     *  rollback all changes
+     * Lastly:
+     *  cleanup transaction intermediate variables.
+     *
+     * @throws TransactionException
+     */
+    @Override
+    protected void doCommit() throws TransactionException {
+        final long txVersion = txNumber.get();
+
+        // collect all changes
+        Set<TinkerElementContainer<TinkerVertex>> changedVertices = txChangedVertices.get();
+        if (null == changedVertices) changedVertices = Collections.emptySet();
+        Set<TinkerElementContainer<TinkerEdge>> changedEdges = txChangedEdges.get();
+        if (null == changedEdges) changedEdges = Collections.emptySet();
+
+        try {
+            // Double-checked locking to reduce lock time
+            if (changedVertices.stream().anyMatch(v -> v.updatedOutsideTransaction()) ||
+                    changedEdges.stream().anyMatch(v -> v.updatedOutsideTransaction()))
+                throw new TransactionException(TX_CONFLICT);
+
+            // try to lock all element containers, throw exception if any element already locked by other tx
+            changedVertices.forEach(v -> {
+                if (!v.tryLock()) throw new TransactionException(TX_CONFLICT);
+            });
+            changedEdges.forEach(e -> {
+                if (!e.tryLock()) throw new TransactionException(TX_CONFLICT);
+            });
+
+            // verify versions of all elements to be sure no element changes during setting lock
+            if (changedVertices.stream().anyMatch(v -> v.updatedOutsideTransaction()) ||
+                    changedEdges.stream().anyMatch(e -> e.updatedOutsideTransaction()))
+                throw new TransactionException(TX_CONFLICT);
+
+            // update indices
+            final TinkerTransactionalIndex vertexIndex = (TinkerTransactionalIndex) graph.vertexIndex;
+            if (vertexIndex != null) vertexIndex.commit(changedVertices);
+            final TinkerTransactionalIndex edgeIndex = (TinkerTransactionalIndex) graph.edgeIndex;
+            if (edgeIndex != null) edgeIndex.commit(changedEdges);
+
+            // commit all changes
+            changedVertices.forEach(v -> v.commit(txVersion));
+            changedEdges.forEach(e -> e.commit(txVersion));
+        } catch (TransactionException ex) {
+            // rollback on error
+            changedVertices.forEach(v -> v.rollback());
+            changedEdges.forEach(e -> e.rollback());
+
+            // also revert indices update
+            final TinkerTransactionalIndex vertexIndex = (TinkerTransactionalIndex) graph.vertexIndex;
+            if (vertexIndex != null) vertexIndex.rollback();
+            final TinkerTransactionalIndex edgeIndex = (TinkerTransactionalIndex) graph.edgeIndex;
+            if (edgeIndex != null) edgeIndex.rollback();
+
+            throw ex;
+        } finally {
+            // remove elements from graph if not used in other tx's
+            changedVertices.stream().filter(v -> v.canBeRemoved()).forEach(v -> graph.vertices.remove(v.getElementId()));
+            changedEdges.stream().filter(e -> e.canBeRemoved()).forEach(e -> graph.edges.remove(e.getElementId()));
+
+            txChangedVertices.remove();
+            txChangedEdges.remove();
+
+            changedVertices.forEach(v -> v.releaseLock());
+            changedEdges.forEach(e -> e.releaseLock());
+
+            txNumber.set(NOT_STARTED);
+        }
+    }
+
+    /**
+     * Rollback all changes made in current transaction.
+     * Workflow:
+     * 1. Rollback all changes. Lock is not needed here because only this thread have access to data.
+     * 2. Rollback indices changes, should be safe.
+     * 3. Cleanup transaction intermediate variables.
+     *
+     * @throws TransactionException
+     */
+    @Override
+    protected void doRollback() throws TransactionException {
+        // rollback for all changed elements
+        Set<TinkerElementContainer<TinkerVertex>> changedVertices = txChangedVertices.get();
+        if (null != changedVertices) changedVertices.forEach(v -> v.rollback());
+        Set<TinkerElementContainer<TinkerEdge>> changedEdges = txChangedEdges.get();
+        if (null != changedEdges) changedEdges.forEach(e -> e.rollback());
+
+        // rollback indices
+        final TinkerTransactionalIndex vertexIndex = (TinkerTransactionalIndex) graph.vertexIndex;
+        if (vertexIndex != null) vertexIndex.rollback();
+        final TinkerTransactionalIndex edgeIndex = (TinkerTransactionalIndex) graph.edgeIndex;
+        if (vertexIndex != null) edgeIndex.rollback();
+
+        // cleanup unused containers
+        if (null != changedVertices)
+            changedVertices.stream().filter(v -> v.canBeRemoved()).forEach(v -> graph.vertices.remove(v.getElementId()));
+        if (null != changedEdges)
+            changedEdges.stream().filter(e -> e.canBeRemoved()).forEach(e -> graph.edges.remove(e.getElementId()));
+
+        txChangedVertices.remove();
+        txChangedEdges.remove();
+
+        txNumber.set(NOT_STARTED);
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraph.java
new file mode 100644
index 0000000000..5e5b3d1036
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraph.java
@@ -0,0 +1,515 @@
+/*
+ * 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.tinkergraph.structure;
+
+import org.apache.commons.configuration2.BaseConfiguration;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Transaction;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
+import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy;
+import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An in-memory (with optional persistence on calls to {@link #close()}), reference implementation of the property
+ * graph interfaces with transaction support provided by TinkerPop.
+ *
+ * @author Valentyn Kahamlyk
+ */
+@Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_STANDARD)
+@Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_INTEGRATE)
+@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD)
+@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER)
+@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_STANDARD)
+@Graph.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_COMPUTER)
+public final class TinkerTransactionGraph extends AbstractTinkerGraph {
+
+    static {
+        TraversalStrategies.GlobalCache.registerStrategies(TinkerTransactionGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies(
+                TinkerGraphStepStrategy.instance(),
+                TinkerGraphCountStrategy.instance()));
+    }
+
+    private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{
+        this.setProperty(Graph.GRAPH, TinkerTransactionGraph.class.getName());
+    }};
+
+    private final TinkerGraphFeatures features = new TinkerGraphFeatures();
+
+    private final TinkerTransaction transaction = new TinkerTransaction(this);
+
+    protected Map<Object, TinkerElementContainer<TinkerVertex>> vertices = new ConcurrentHashMap<>();
+    protected Map<Object, TinkerElementContainer<TinkerEdge>> edges = new ConcurrentHashMap<>();
+
+    /**
+     * An empty private constructor that initializes {@link TinkerTransactionGraph}.
+     */
+    private TinkerTransactionGraph(final Configuration configuration) {
+        this.configuration = configuration;
+        vertexIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, Vertex.class);
+        edgeIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, Edge.class);
+        vertexPropertyIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, VertexProperty.class);
+        defaultVertexPropertyCardinality = VertexProperty.Cardinality.valueOf(
+                configuration.getString(GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.single.name()));
+        allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, false);
+
+        graphLocation = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null);
+        graphFormat = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_FORMAT, null);
+
+        if ((graphLocation != null && null == graphFormat) || (null == graphLocation && graphFormat != null))
+            throw new IllegalStateException(String.format("The %s and %s must both be specified if either is present",
+                    GREMLIN_TINKERGRAPH_GRAPH_LOCATION, GREMLIN_TINKERGRAPH_GRAPH_FORMAT));
+
+        if (graphLocation != null) loadGraph();
+
+        serviceRegistry = new TinkerServiceRegistry(this);
+        configuration.getList(String.class, GREMLIN_TINKERGRAPH_SERVICE, Collections.emptyList()).forEach(serviceClass ->
+                serviceRegistry.registerService(instantiate(serviceClass)));
+    }
+
+    /**
+     * Open a new {@link TinkerTransactionGraph} instance.
+     * <p/>
+     * <b>Reference Implementation Help:</b> If a {@link Graph} implementation does not require a {@code Configuration}
+     * (or perhaps has a default configuration) it can choose to implement a zero argument
+     * {@code open()} method. This is an optional constructor method for TinkerGraph. It is not enforced by the Gremlin
+     * Test Suite.
+     */
+    public static TinkerTransactionGraph open() {
+        return open(EMPTY_CONFIGURATION);
+    }
+
+    /**
+     * Open a new {@code TinkerGraph} instance.
+     * <p/>
+     * <b>Reference Implementation Help:</b> This method is the one use by the {@link GraphFactory} to instantiate
+     * {@link Graph} instances.  This method must be overridden for the Structure Test Suite to pass. Implementers have
+     * latitude in terms of how exceptions are handled within this method.  Such exceptions will be considered
+     * implementation specific by the test suite as all test generate graph instances by way of
+     * {@link GraphFactory}. As such, the exceptions get generalized behind that facade and since
+     * {@link GraphFactory} is the preferred method to opening graphs it will be consistent at that level.
+     *
+     * @param configuration the configuration for the instance
+     * @return a newly opened {@link Graph}
+     */
+    public static TinkerTransactionGraph open(final Configuration configuration) {
+        return new TinkerTransactionGraph(configuration);
+    }
+
+    ////////////// STRUCTURE API METHODS //////////////////
+
+    @Override
+    public Vertex addVertex(final Object... keyValues) {
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
+
+        Object idValue = vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
+        if (null == idValue)
+            idValue = vertexIdManager.getNextId(this);
+        final String label = ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL);
+
+        this.tx().readWrite();
+        final long txNumber = transaction.getTxNumber();
+
+        final TinkerElementContainer<TinkerVertex> newContainer = new TinkerElementContainer<>(idValue);
+        // try to add new container or get existing
+        TinkerElementContainer<TinkerVertex> container = vertices.putIfAbsent(idValue, newContainer);
+
+        // is existing container contains Vertex?
+        if (container != null && container.get() != null)
+            throw Exceptions.vertexWithIdAlreadyExists(idValue);
+
+        // no existing container, let's use new one
+        if (container == null)
+            container = newContainer;
+
+        final TinkerVertex vertex = new TinkerVertex(idValue, label, this, txNumber);
+        ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, keyValues);
+        container.setDraft(vertex, (TinkerTransaction) tx());
+
+        return vertex;
+    }
+
+    @Override
+    public void removeVertex(final Object vertexId) {
+        if (!vertices.containsKey(vertexId)) return;
+
+        // vertex can be deleted in other thread, so need to double-check
+        final TinkerElementContainer<?> container = vertices.get(vertexId);
+        if (null != container)
+            container.markDeleted((TinkerTransaction) tx());
+    }
+
+    @Override
+    public void touch(final TinkerVertex vertex) {
+        // already removed, so skip
+        if (null == vertex || !vertices.containsKey(vertex.id())) return;
+
+        final TinkerElementContainer<TinkerVertex> container = vertices.get(vertex.id());
+
+        if (null != container) {
+            this.tx().readWrite();
+            container.touch(vertex, (TinkerTransaction) tx());
+        }
+    };
+
+    @Override
+    public void touch(final TinkerEdge edge) {
+        // already removed, so skip
+        if (null == edge || !edges.containsKey(edge.id())) return;
+
+        final TinkerElementContainer<TinkerEdge> container = edges.get(edge.id());
+
+        if (null != container) {
+            this.tx().readWrite();
+            container.touch(edge, (TinkerTransaction) tx());
+        }
+    };
+
+    @Override
+    public Edge addEdge(final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues) {
+        ElementHelper.validateLabel(label);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
+
+        Object idValue = edgeIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
+        if (null == idValue)
+            idValue = edgeIdManager.getNextId(this);
+
+        this.tx().readWrite();
+        final long txNumber = transaction.getTxNumber();
+
+        final TinkerElementContainer<TinkerEdge> newContainer = new TinkerElementContainer<>(idValue);
+        // try to add new container or get existing
+        TinkerElementContainer<TinkerEdge> container = edges.putIfAbsent(idValue, newContainer);
+
+        // is existing container contains Vertex?
+        if (container != null && container.get() != null)
+            throw Exceptions.vertexWithIdAlreadyExists(idValue);
+
+        // no existing container, let's use new one
+        if (container == null)
+            container = newContainer;
+
+        final TinkerEdge edge = new TinkerEdge(idValue, outVertex, label, inVertex, txNumber);
+        ElementHelper.attachProperties(edge, keyValues);
+        container.setDraft(edge, (TinkerTransaction) tx());
+
+        addOutEdge(outVertex, label, edge);
+        addInEdge(inVertex, label, edge);
+
+        return edge;
+    }
+
+    @Override
+    public void removeEdge(final Object edgeId) {
+        if (!edges.containsKey(edgeId)) return;
+
+        final TinkerElementContainer<TinkerEdge> container = edges.get(edgeId);
+
+        if (null == container || container.isDeleted()) return;
+
+        final TinkerEdge edge = container.get();
+
+        if (edge == null) return;
+
+        final TinkerVertex outVertex = (TinkerVertex) edge.outVertex();
+        touch(outVertex);
+        final TinkerVertex inVertex = (TinkerVertex) edge.inVertex();
+        touch(inVertex);
+
+        if (null != outVertex && null != outVertex.outEdgesId) {
+            final Set<Object> edges = outVertex.outEdgesId.get(edge.label());
+            if (null != edges) {
+                edges.removeIf(e -> e == edge.id());
+            }
+        }
+        if (null != inVertex && null != inVertex.inEdgesId) {
+            final Set<Object> edges = inVertex.inEdgesId.get(edge.label());
+            if (null != edges) {
+                edges.removeIf(e -> e == edge.id());
+            }
+        }
+
+        container.markDeleted((TinkerTransaction) tx());
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+        this.vertices.clear();
+        this.edges.clear();
+    }
+
+    @Override
+    public Transaction tx() {
+        return transaction;
+    }
+
+    @Override
+    public int getVerticesCount() {
+        return (int) vertices.entrySet().stream().filter(v -> v.getValue().get() != null).count();
+    }
+
+    @Override
+    public boolean hasVertex(Object id) {
+        return null != vertex(id);
+    }
+
+    Map<Object, TinkerElementContainer<TinkerVertex>> getVertices () { return vertices; }
+
+    @Override
+    public int getEdgesCount() {
+        return (int) edges.entrySet().stream().filter(v -> v.getValue().get() != null).count();
+    }
+
+    @Override
+    public boolean hasEdge(Object id) {
+        return null != edge(id);
+    }
+
+    Map<Object, TinkerElementContainer<TinkerEdge>> getEdges () { return edges; }
+
+    @Override
+    public TinkerServiceRegistry getServiceRegistry() {
+        return serviceRegistry;
+    }
+
+    @Override
+    public Vertex vertex(final Object vertexId) {
+        final TinkerElementContainer<TinkerVertex> container = vertices.get(vertexIdManager.convert(vertexId));
+        return container == null ? null : container.getWithClone();
+    }
+
+    @Override
+    public Iterator<Vertex> vertices(final Object... vertexIds) {
+        return createElementIterator(Vertex.class, vertices, vertexIdManager, vertexIds);
+    }
+
+    @Override
+    public Edge edge(final Object edgeId) {
+        final TinkerElementContainer<TinkerEdge> container = edges.get(edgeIdManager.convert(edgeId));
+        return container == null ? null : container.getWithClone();
+    }
+
+    @Override
+    public Iterator<Edge> edges(final Object... edgeIds) {
+        return createElementIterator(Edge.class, edges, edgeIdManager, edgeIds);
+    }
+
+    private <T extends Element, C extends TinkerElement> Iterator<T> createElementIterator(final Class<T> clazz,
+                                                                  final Map<Object, TinkerElementContainer<C>> elements,
+                                                                  final IdManager idManager,
+
+                                                                  final Object... ids) {
+        this.tx().readWrite();
+
+        final Iterator<T> iterator;
+        if (0 == ids.length) {
+            iterator = new TinkerGraphIterator<>(
+                    // todo: clone only if traversal contains mutating steps
+                    elements.values().stream().map(c -> (T) c.getWithClone()).filter(e -> e != null).iterator());
+        } else {
+            final List<Object> idList = Arrays.asList(ids);
+
+            // TinkerGraph can take a Vertex/Edge or any object as an "id". If it is an Element then we just cast
+            // to that type and pop off the identifier. there is no need to pass that through the IdManager since
+            // the assumption is that if it's already an Element, its identifier must be valid to the Graph and to
+            // its associated IdManager. All other objects are passed to the IdManager for conversion.
+            return new TinkerGraphIterator<>(IteratorUtils.filter(IteratorUtils.map(idList, id -> {
+                // ids cant be null so all of those filter out
+                if (null == id) return null;
+                final Object iid = clazz.isAssignableFrom(id.getClass()) ? clazz.cast(id).id() : idManager.convert(id);
+                final TinkerElementContainer<C> container = elements.get(iid);
+                return container == null ? null : (T) container.getWithClone();
+            }).iterator(), Objects::nonNull));
+        }
+        return TinkerHelper.inComputerMode(this) ?
+                (Iterator<T>) (clazz.equals(Vertex.class) ?
+                        IteratorUtils.filter((Iterator<Vertex>) iterator, t -> this.graphComputerView.legalVertex(t)) :
+                        IteratorUtils.filter((Iterator<Edge>) iterator, t -> this.graphComputerView.legalEdge(t.outVertex(), t))) :
+                iterator;
+    }
+    @Override
+    protected void addOutEdge(final TinkerVertex vertex, final String label, final Edge edge) {
+        touch(vertex);
+        if (null == vertex.outEdgesId) vertex.outEdgesId = new ConcurrentHashMap<>();
+        Set<Object> edges = vertex.outEdgesId.get(label);
+        if (null == edges) {
+            edges = ConcurrentHashMap.newKeySet();
+            vertex.outEdgesId.put(label, edges);
+        }
+        edges.add(edge.id());
+    }
+
+    @Override
+    protected void addInEdge(final TinkerVertex vertex, final String label, final Edge edge) {
+        touch(vertex);
+        if (null == vertex.inEdgesId) vertex.inEdgesId = new ConcurrentHashMap<>();
+        Set<Object> edges = vertex.inEdgesId.get(label);
+        if (null == edges) {
+            edges = ConcurrentHashMap.newKeySet();;
+            vertex.inEdgesId.put(label, edges);
+        }
+        edges.add(edge.id());
+    }
+
+    /**
+     * Return TinkerGraph feature set.
+     * <p/>
+     * <b>Reference Implementation Help:</b> Implementers only need to implement features for which there are
+     * negative or instance configured features.  By default, all {@link Features} return true.
+     */
+    @Override
+    public Features features() {
+        return features;
+    }
+
+    public class TinkerGraphFeatures implements Features {
+
+        private final TinkerGraphGraphFeatures graphFeatures = new TinkerGraphGraphFeatures();
+        private final TinkerGraphEdgeFeatures edgeFeatures = new TinkerGraphEdgeFeatures();
+        private final TinkerGraphVertexFeatures vertexFeatures = new TinkerGraphVertexFeatures();
+
+        private TinkerGraphFeatures() {
+        }
+
+        @Override
+        public GraphFeatures graph() {
+            return graphFeatures;
+        }
+
+        @Override
+        public EdgeFeatures edge() {
+            return edgeFeatures;
+        }
+
+        @Override
+        public VertexFeatures vertex() {
+            return vertexFeatures;
+        }
+
+        @Override
+        public String toString() {
+            return StringFactory.featureString(this);
+        }
+
+    }
+
+    public class TinkerGraphGraphFeatures implements Features.GraphFeatures {
+
+        private TinkerGraphGraphFeatures() {
+        }
+
+        @Override
+        public boolean supportsConcurrentAccess() {
+            return false;
+        }
+
+        @Override
+        public boolean supportsThreadedTransactions() {
+            return false;
+        }
+
+        @Override
+        public boolean supportsTransactions() {
+            return true;
+        }
+
+        @Override
+        public boolean supportsServiceCall() {
+            return true;
+        }
+
+    }
+
+
+    /////////////// GRAPH SPECIFIC INDEXING METHODS ///////////////
+
+    /**
+     * Create an index for said element class ({@link Vertex} or {@link Edge}) and said property key.
+     * Whenever an element has the specified key mutated, the index is updated.
+     * When the index is created, all existing elements are indexed to ensure that they are captured by the index.
+     *
+     * @param key          the property key to index
+     * @param elementClass the element class to index
+     * @param <E>          The type of the element class
+     */
+    public <E extends Element> void createIndex(final String key, final Class<E> elementClass) {
+        if (Vertex.class.isAssignableFrom(elementClass)) {
+            if (null == this.vertexIndex) this.vertexIndex = new TinkerTransactionalIndex<>(this, TinkerVertex.class);
+            this.vertexIndex.createKeyIndex(key);
+        } else if (Edge.class.isAssignableFrom(elementClass)) {
+            if (null == this.edgeIndex) this.edgeIndex = new TinkerTransactionalIndex<>(this, TinkerEdge.class);
+            this.edgeIndex.createKeyIndex(key);
+        } else {
+            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
+        }
+    }
+
+    /**
+     * Drop the index for the specified element class ({@link Vertex} or {@link Edge}) and key.
+     *
+     * @param key          the property key to stop indexing
+     * @param elementClass the element class of the index to drop
+     * @param <E>          The type of the element class
+     */
+    public <E extends Element> void dropIndex(final String key, final Class<E> elementClass) {
+        if (Vertex.class.isAssignableFrom(elementClass)) {
+            if (null != this.vertexIndex) this.vertexIndex.dropKeyIndex(key);
+        } else if (Edge.class.isAssignableFrom(elementClass)) {
+            if (null != this.edgeIndex) this.edgeIndex.dropKeyIndex(key);
+        } else {
+            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
+        }
+    }
+
+    /**
+     * Return all the keys currently being index for said element class  ({@link Vertex} or {@link Edge}).
+     *
+     * @param elementClass the element class to get the indexed keys for
+     * @param <E>          The type of the element class
+     * @return the set of keys currently being indexed
+     */
+    public <E extends Element> Set<String> getIndexedKeys(final Class<E> elementClass) {
+        if (Vertex.class.isAssignableFrom(elementClass)) {
+            return null == this.vertexIndex ? Collections.emptySet() : this.vertexIndex.getIndexedKeys();
+        } else if (Edge.class.isAssignableFrom(elementClass)) {
+            return null == this.edgeIndex ? Collections.emptySet() : this.edgeIndex.getIndexedKeys();
+        } else {
+            throw new IllegalArgumentException("Class is not indexable: " + elementClass);
+        }
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionalIndex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionalIndex.java
new file mode 100644
index 0000000000..9f3a19eff0
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionalIndex.java
@@ -0,0 +1,239 @@
+/*
+ * 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.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author Valentyn Kahamlyk
+ */
+final class TinkerTransactionalIndex<T extends TinkerElement> extends AbstractTinkerIndex<T> {
+
+    protected Map<String, Map<Object, Set<TinkerElementContainer<T>>>> index = new ConcurrentHashMap<>();
+    protected ThreadLocal<Map<String, Map<Object, Set<T>>>> txIndex =
+            ThreadLocal.withInitial(() -> new ConcurrentHashMap<>());
+
+    public TinkerTransactionalIndex(final TinkerTransactionGraph graph, final Class<T> indexClass) {
+        super(graph, indexClass);
+    }
+
+    private void putTxElement(final String key, final Object value, final T element) {
+        Map<String, Map<Object, Set<T>>> index = txIndex.get();
+        if (index == null) {
+            txIndex.set(new HashMap<>());
+            index = txIndex.get();
+        }
+
+        Map<Object, Set<T>> keyMap = index.get(key);
+        if (null == keyMap) {
+            index.putIfAbsent(key, new ConcurrentHashMap<>());
+            keyMap = index.get(key);
+        }
+        final Object indexableValue = indexable(value);
+        Set<T> objects = keyMap.get(indexableValue);
+        if (null == objects) {
+            keyMap.putIfAbsent(indexableValue, ConcurrentHashMap.newKeySet());
+            objects = keyMap.get(indexableValue);
+        }
+        objects.add(element);
+    }
+
+    private List<T> getNotModifiedElements(final String key, final Object value) {
+        final Map<Object, Set<TinkerElementContainer<T>>> keyMap = index.get(key);
+        if (null == keyMap)
+            return new ArrayList<>();
+
+        Set<TinkerElementContainer<T>> set = keyMap.get(indexable(value));
+        if (null == set)
+            return new ArrayList<>();
+
+        return set.stream().
+                filter(e -> !e.isChanged() && e.get() != null).
+                map(e -> e.get()).collect(Collectors.toList());
+    }
+
+    private Set<T> getModifiedElements(final String key, final Object value) {
+        final Map<String, Map<Object, Set<T>>> index = txIndex.get();
+        if (null == index)
+            return null;
+
+        final Map<Object, Set<T>> keyMap = index.get(key);
+        if (null == keyMap)
+            return null;
+
+        return keyMap.get(indexable(value));
+    }
+
+    @Override
+    public List<T> get(final String key, final Object value) {
+        final List<T> notModifiedElements = getNotModifiedElements(key, value);
+        final Set<T> modifiedElements = getModifiedElements(key, value);
+        if (modifiedElements != null)
+            modifiedElements.forEach(e -> {
+                if (!notModifiedElements.contains(e)) notModifiedElements.add(e);
+            });
+
+        return notModifiedElements;
+    }
+
+    @Override
+    public long count(final String key, final Object value) {
+        return get(key, value).size();
+    }
+
+    @Override
+    public void remove(final String key, final Object value, final T element) {
+        final Map<String, Map<Object, Set<T>>> index = txIndex.get();
+        if (null == index) return;
+
+        final Map<Object, Set<T>> keyMap = index.get(key);
+        if (null != keyMap) {
+            final Set<T> objects = keyMap.get(indexable(value));
+            if (null != objects) {
+                objects.remove(element);
+                if (objects.isEmpty())
+                    keyMap.remove(indexable(value));
+            }
+        }
+    }
+
+    @Override
+    public void removeElement(final T element) {
+        if (indexClass.isAssignableFrom(element.getClass())) {
+            element.properties().forEachRemaining(p -> {
+                        if (p.isPresent() && indexedKeys.contains(p.key())) {
+                            remove(p.key(), p.value(), element);
+                        }
+                    }
+            );
+        }
+    }
+
+    private void put(final String key, final Object value, final TinkerElementContainer<T> container) {
+        Map<Object, Set<TinkerElementContainer<T>>> keyMap = index.get(key);
+        if (null == keyMap) {
+            index.putIfAbsent(key, new ConcurrentHashMap<>());
+            keyMap = index.get(key);
+        }
+        final Object indexableValue = indexable(value);
+        Set<TinkerElementContainer<T>> objects = keyMap.get(indexableValue);
+        if (null == objects) {
+            keyMap.putIfAbsent(indexableValue, ConcurrentHashMap.newKeySet());
+            objects = keyMap.get(indexableValue);
+        }
+        objects.add(container);
+    }
+
+    private void addContainer(final TinkerElementContainer<T> container) {
+        final T element = container.get();
+        if (!indexClass.isAssignableFrom(element.getClass()) || !element.properties().hasNext())
+            return;
+
+        element.properties().forEachRemaining(p -> {
+                    if (p.isPresent() && indexedKeys.contains(p.key())) {
+                        put(p.key(), p.value(), container);
+                    }
+                }
+        );
+    }
+
+    @Override
+    public void autoUpdate(final String key, final Object newValue, final Object oldValue, final T element) {
+        if (indexedKeys.contains(key)) {
+            remove(key, oldValue, element);
+            putTxElement(key, newValue, element);
+        }
+    }
+
+    @Override
+    public void createKeyIndex(final String key) {
+        if (null == key)
+            throw Graph.Exceptions.argumentCanNotBeNull("key");
+        if (key.isEmpty())
+            throw new IllegalArgumentException("The key for the index cannot be an empty string");
+
+        if (indexedKeys.contains(key))
+            return;
+        indexedKeys.add(key);
+
+        final Map elements =
+                Vertex.class.isAssignableFrom(indexClass) ?
+                        ((TinkerTransactionGraph) graph).vertices :
+                        ((TinkerTransactionGraph) graph).edges;
+
+        for (Object element : elements.values()) {
+            addContainer((TinkerElementContainer<T>) element);
+        }
+    }
+
+    @Override
+    public void dropKeyIndex(final String key) {
+        if (index.containsKey(key))
+            index.remove(key).clear();
+
+        final Map<String, Map<Object, Set<T>>> index = txIndex.get();
+        if (index != null && index.containsKey(key))
+            index.remove(key).clear();
+
+        indexedKeys.remove(key);
+    }
+
+    private void removeContainer(TinkerElementContainer<T> container) {
+        final T element = container.getUnmodified();
+        if (element == null || !indexClass.isAssignableFrom(element.getClass()) || !element.properties().hasNext())
+            return;
+
+        element.properties().forEachRemaining(p -> {
+            final Map<Object, Set<TinkerElementContainer<T>>> keyMap = index.get(p.key());
+            final Object indexableValue = indexable(p.value());
+            if (null != keyMap) {
+                final Set<TinkerElementContainer<T>> objects = keyMap.get(indexableValue);
+                if (null != objects) {
+                    objects.remove(container);
+                    if (objects.isEmpty())
+                        keyMap.remove(indexableValue);
+                }
+            }
+        });
+    }
+
+    public void commit(final Set<TinkerElementContainer<T>> updatedElements) {
+        for (final TinkerElementContainer<T> element : updatedElements) {
+            removeContainer(element);
+            if (!element.isDeleted())
+                // todo: compare and update only changed properties
+                addContainer(element);
+        }
+
+        txIndex.remove();
+    }
+
+    public void rollback() {
+        txIndex.remove();
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index 1765054efb..3e1f131e89 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -25,16 +25,17 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 /**
@@ -43,17 +44,64 @@ import java.util.stream.Collectors;
 public final class TinkerVertex extends TinkerElement implements Vertex {
 
     protected Map<String, List<VertexProperty>> properties;
+    // Edges should be used by non-transaction Graph due to performance
     protected Map<String, Set<Edge>> outEdges;
     protected Map<String, Set<Edge>> inEdges;
-    private final TinkerGraph graph;
+    // Edge ids are for transactional Graph
+    protected Map<String, Set<Object>> outEdgesId;
+    protected Map<String, Set<Object>> inEdgesId;
+    private final AbstractTinkerGraph graph;
     private boolean allowNullPropertyValues;
+    private final boolean isTxMode;
 
-    protected TinkerVertex(final Object id, final String label, final TinkerGraph graph) {
+    protected TinkerVertex(final Object id, final String label, final AbstractTinkerGraph graph) {
         super(id, label);
         this.graph = graph;
+        this.isTxMode = graph instanceof TinkerTransactionGraph;
         this.allowNullPropertyValues = graph.features().vertex().supportsNullPropertyValues();
     }
 
+    protected TinkerVertex(final Object id, final String label, final AbstractTinkerGraph graph, final long currentVersion) {
+        super(id, label, currentVersion);
+        this.graph = graph;
+        this.isTxMode = graph instanceof TinkerTransactionGraph;
+        this.allowNullPropertyValues = graph.features().vertex().supportsNullPropertyValues();
+    }
+
+    @Override
+    public Object clone() {
+        if (!isTxMode) {
+            final TinkerVertex vertex = new TinkerVertex(id, label, graph, currentVersion);
+            vertex.inEdgesId = inEdgesId;
+            vertex.outEdgesId = outEdgesId;
+            vertex.properties = properties;
+            return vertex;
+        }
+
+        final TinkerVertex vertex = new TinkerVertex(id, label, graph, currentVersion);
+        if (inEdgesId != null)
+            vertex.inEdgesId = CollectionUtil.clone((ConcurrentHashMap<String, Set<Object>>) inEdgesId);
+
+        if (outEdgesId != null)
+            vertex.outEdgesId = CollectionUtil.clone((ConcurrentHashMap<String, Set<Object>>) outEdgesId);
+
+        if (properties != null) {
+            final ConcurrentHashMap<String, List<VertexProperty>> result = new ConcurrentHashMap<>(properties.size());
+
+            // clone will not work because TinkerVertexProperty contains link to Vertex
+            for (Map.Entry<String, List<VertexProperty>> entry : properties.entrySet()) {
+                final List<VertexProperty> clonedValue = entry.getValue().stream()
+                        .map(vp ->((TinkerVertexProperty) vp).copy(vertex))
+                        .collect(Collectors.toList());
+
+                result.put(entry.getKey(), clonedValue);
+            }
+            vertex.properties = result;
+        }
+
+        return vertex;
+    }
+
     @Override
     public Graph graph() {
         return this.graph;
@@ -72,7 +120,7 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
                 throw Vertex.Exceptions.multiplePropertiesExistForProvidedKey(key);
         } else {
             if (this.properties != null && this.properties.containsKey(key)) {
-                final List<VertexProperty> list = (List) this.properties.get(key);
+                final List<VertexProperty> list = this.properties.get(key);
                 if (list.size() > 1)
                     throw Vertex.Exceptions.multiplePropertiesExistForProvidedKey(key);
                 else
@@ -84,6 +132,8 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
 
     @Override
     public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
+        graph.touch(this);
+
         if (this.removed) throw elementAlreadyRemoved(Vertex.class, id);
         ElementHelper.legalPropertyKeyValueArray(keyValues);
         ElementHelper.validateProperty(key, value);
@@ -112,11 +162,11 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
 
             final VertexProperty<V> vertexProperty = new TinkerVertexProperty<V>(idValue, this, key, value);
 
-            if (null == this.properties) this.properties = new HashMap<>();
+            if (null == this.properties) this.properties = new ConcurrentHashMap<>();
             final List<VertexProperty> list = this.properties.getOrDefault(key, new ArrayList<>());
             list.add(vertexProperty);
             this.properties.put(key, list);
-            TinkerHelper.autoUpdateIndex(this, key, value, null);
+            TinkerIndexHelper.autoUpdateIndex(this, key, value, null);
             ElementHelper.attachProperties(vertexProperty, keyValues);
             return vertexProperty;
         }
@@ -125,7 +175,7 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
     @Override
     public Set<String> keys() {
         if (null == this.properties) return Collections.emptySet();
-        return TinkerHelper.inComputerMode((TinkerGraph) graph()) ?
+        return TinkerHelper.inComputerMode((AbstractTinkerGraph) graph()) ?
                 Vertex.super.keys() :
                 this.properties.keySet();
     }
@@ -133,18 +183,21 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
     @Override
     public Edge addEdge(final String label, final Vertex vertex, final Object... keyValues) {
         if (null == vertex) throw Graph.Exceptions.argumentCanNotBeNull("vertex");
-        if (this.removed) throw elementAlreadyRemoved(Vertex.class, this.id);
-        return TinkerHelper.addEdge(this.graph, this, (TinkerVertex) vertex, label, keyValues);
+        if (this.removed || ((TinkerVertex) vertex).removed) throw elementAlreadyRemoved(Vertex.class, this.id);
+
+        return graph.addEdge(this, (TinkerVertex) vertex, label, keyValues);
     }
 
     @Override
     public void remove() {
+        graph.touch(this);
+
         final List<Edge> edges = new ArrayList<>();
-        this.edges(Direction.BOTH).forEachRemaining(edges::add);
+        this.edges(Direction.BOTH).forEachRemaining(edge -> edges.add(edge));
         edges.stream().filter(edge -> !((TinkerEdge) edge).removed).forEach(Edge::remove);
+        TinkerIndexHelper.removeElementIndex(this);
         this.properties = null;
-        TinkerHelper.removeElementIndex(this);
-        this.graph.vertices.remove(this.id);
+        this.graph.removeVertex(this.id);
         this.removed = true;
     }
 
@@ -155,7 +208,9 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
 
     @Override
     public Iterator<Edge> edges(final Direction direction, final String... edgeLabels) {
-        final Iterator<Edge> edgeIterator = (Iterator) TinkerHelper.getEdges(this, direction, edgeLabels);
+        final Iterator<Edge> edgeIterator = isTxMode
+                ? (Iterator) TinkerHelper.getEdgesTx(this, direction, edgeLabels)
+                : (Iterator) TinkerHelper.getEdges(this, direction, edgeLabels);
         return TinkerHelper.inComputerMode(this.graph) ?
                 IteratorUtils.filter(edgeIterator, edge -> this.graph.graphComputerView.legalEdge(this, edge)) :
                 edgeIterator;
@@ -163,23 +218,28 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
 
     @Override
     public Iterator<Vertex> vertices(final Direction direction, final String... edgeLabels) {
-        return TinkerHelper.inComputerMode(this.graph) ?
-                direction.equals(Direction.BOTH) ?
-                        IteratorUtils.concat(
-                                IteratorUtils.map(this.edges(Direction.OUT, edgeLabels), Edge::inVertex),
-                                IteratorUtils.map(this.edges(Direction.IN, edgeLabels), Edge::outVertex)) :
-                        IteratorUtils.map(this.edges(direction, edgeLabels), edge -> edge.vertices(direction.opposite()).next()) :
-                (Iterator) TinkerHelper.getVertices(this, direction, edgeLabels);
+        if (TinkerHelper.inComputerMode(this.graph))
+            return direction.equals(Direction.BOTH) ?
+                    IteratorUtils.concat(
+                            IteratorUtils.map(this.edges(Direction.OUT, edgeLabels), Edge::inVertex),
+                            IteratorUtils.map(this.edges(Direction.IN, edgeLabels), Edge::outVertex)) :
+                    IteratorUtils.map(this.edges(direction, edgeLabels), edge -> edge.vertices(direction.opposite()).next());
+
+        return isTxMode
+                ? (Iterator) TinkerHelper.getVerticesTx(this, direction, edgeLabels)
+                : (Iterator) TinkerHelper.getVertices(this, direction, edgeLabels);
     }
 
     @Override
     public <V> Iterator<VertexProperty<V>> properties(final String... propertyKeys) {
         if (this.removed) return Collections.emptyIterator();
-        if (TinkerHelper.inComputerMode((TinkerGraph) graph()))
-            return (Iterator) ((TinkerGraph) graph()).graphComputerView.getProperties(TinkerVertex.this).stream().filter(p -> ElementHelper.keyExists(p.key(), propertyKeys)).iterator();
+        if (TinkerHelper.inComputerMode((AbstractTinkerGraph) graph()))
+            return (Iterator) ((AbstractTinkerGraph) graph()).graphComputerView.getProperties(TinkerVertex.this).stream().filter(p -> ElementHelper.keyExists(p.key(), propertyKeys)).iterator();
         else {
             if (null == this.properties) return Collections.emptyIterator();
             if (propertyKeys.length == 1) {
+                if (null == propertyKeys[0])
+                    return Collections.emptyIterator();
                 final List<VertexProperty> properties = this.properties.getOrDefault(propertyKeys[0], Collections.emptyList());
                 if (properties.size() == 1) {
                     return IteratorUtils.of(properties.get(0));
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
index f2635dfe20..64a840fd32 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
@@ -32,6 +32,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
@@ -52,7 +53,7 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
      * with {@link TinkerGraphComputerView}.
      */
     public TinkerVertexProperty(final TinkerVertex vertex, final String key, final V value, final Object... propertyKeyValues) {
-        this(((TinkerGraph) vertex.graph()).vertexPropertyIdManager.getNextId((TinkerGraph) vertex.graph()), vertex, key, value, propertyKeyValues);
+        this(((AbstractTinkerGraph) vertex.graph()).vertexPropertyIdManager.getNextId((AbstractTinkerGraph) vertex.graph()), vertex, key, value, propertyKeyValues);
     }
 
     /**
@@ -92,6 +93,26 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
         return StringFactory.propertyString(this);
     }
 
+    @Override
+    public Object clone() {
+        final TinkerVertexProperty vp = new TinkerVertexProperty(id, vertex, key, value);
+        vp.properties = properties;
+        return vp;
+    }
+
+    public TinkerVertexProperty copy(final TinkerVertex newOwner) {
+        final TinkerVertexProperty vp = new TinkerVertexProperty(id, newOwner, key, value);
+
+        if (null != properties) {
+            final Map<String, Property> cloned = new ConcurrentHashMap<>(properties.size());
+            properties.entrySet().stream().forEach(p -> cloned.put(p.getKey(), ((TinkerProperty) p.getValue()).copy(vp)));
+
+            vp.properties = cloned;
+        }
+
+        return vp;
+    }
+
     @Override
     public Object id() {
         return this.id;
@@ -115,6 +136,9 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
 
     @Override
     public <U> Property<U> property(final String key, final U value) {
+        // in most cases vertex should already be touched, but let's play safe
+        ((AbstractTinkerGraph)vertex.graph()).touch(vertex);
+
         if (this.removed) throw elementAlreadyRemoved(VertexProperty.class, id);
 
         if ((!allowNullPropertyValues && null == value)) {
@@ -136,10 +160,12 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
     @Override
     public void remove() {
         if (null != this.vertex.properties && this.vertex.properties.containsKey(this.key)) {
+            ((AbstractTinkerGraph)vertex.graph()).touch(vertex);
+
             this.vertex.properties.get(this.key).remove(this);
             if (this.vertex.properties.get(this.key).size() == 0) {
                 this.vertex.properties.remove(this.key);
-                TinkerHelper.removeIndex(this.vertex, this.key, this.value);
+                TinkerIndexHelper.removeIndex(this.vertex, this.key, this.value);
             }
             final AtomicBoolean delete = new AtomicBoolean(true);
             this.vertex.properties(this.key).forEachRemaining(property -> {
@@ -147,7 +173,7 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
                 if ((currentPropertyValue != null && currentPropertyValue.equals(this.value) || null == currentPropertyValue && null == this.value))
                     delete.set(false);
             });
-            if (delete.get()) TinkerHelper.removeIndex(this.vertex, this.key, this.value);
+            if (delete.get()) TinkerIndexHelper.removeIndex(this.vertex, this.key, this.value);
             this.properties = null;
             this.removed = true;
         }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java
index ed32a1c0f0..4fddf7a366 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.tinkergraph;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
+import com.google.inject.Provides;
 import com.google.inject.Stage;
 import io.cucumber.guice.CucumberModules;
 import io.cucumber.junit.Cucumber;
@@ -46,7 +47,12 @@ public class TinkerGraphAllowNullFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.NullWorld.class);
+            bind(World.class).to(TinkerWorld.NullWorld.class);
+        }
+
+        @Provides
+        static TinkerWorld.NullWorld provideNullWorld() {
+            return new TinkerWorld.NullWorld(new TinkerWorld.TinkerGraphWorld());
         }
     }
 }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java
index 8083e95808..c519e1c416 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.tinkergraph;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
+import com.google.inject.Provides;
 import com.google.inject.Stage;
 import io.cucumber.guice.CucumberModules;
 import io.cucumber.junit.Cucumber;
@@ -46,7 +47,12 @@ public class TinkerGraphComputerFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.ComputerWorld.class);
+            bind(World.class).to(TinkerWorld.ComputerWorld.class);
+        }
+
+        @Provides
+        static TinkerWorld.ComputerWorld provideComputerWorld() {
+            return new TinkerWorld.ComputerWorld(new TinkerWorld.TinkerGraphWorld());
         }
     }
 }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
index da655d5437..8f03a7e991 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
@@ -46,7 +46,7 @@ public class TinkerGraphFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.class);
+            bind(World.class).to(TinkerWorld.TinkerGraphWorld.class);
         }
     }
 }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
deleted file mode 100644
index ee915acc2d..0000000000
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
+++ /dev/null
@@ -1,148 +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.tinkergraph;
-
-import io.cucumber.java.Scenario;
-import org.apache.commons.configuration2.BaseConfiguration;
-import org.apache.commons.configuration2.Configuration;
-import org.apache.commons.configuration2.MapConfiguration;
-import org.apache.tinkerpop.gremlin.LoadGraphWith;
-import org.apache.tinkerpop.gremlin.TestHelper;
-import org.apache.tinkerpop.gremlin.features.World;
-import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
-import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer;
-import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerDegreeCentralityFactory;
-import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerTextSearchFactory;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
-import org.junit.AssumptionViolatedException;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Random;
-import java.util.stream.Collectors;
-
-/**
- * The {@link World} implementation for TinkerGraph that provides the {@link GraphTraversalSource} instances required
- * by the Gherkin test suite.
- */
-public class TinkerGraphWorld implements World {
-    private static final TinkerGraph modern = registerTestServices(TinkerFactory.createModern());
-    private static final TinkerGraph classic = registerTestServices(TinkerFactory.createClassic());
-    private static final TinkerGraph crew = registerTestServices(TinkerFactory.createTheCrew());
-    private static final TinkerGraph sink = registerTestServices(TinkerFactory.createKitchenSink());
-    private static final TinkerGraph grateful = registerTestServices(TinkerFactory.createGratefulDead());
-
-    private static TinkerGraph registerTestServices(final TinkerGraph graph) {
-        graph.getServiceRegistry().registerService(new TinkerTextSearchFactory(graph));
-        graph.getServiceRegistry().registerService(new TinkerDegreeCentralityFactory(graph));
-        return graph;
-    }
-
-    @Override
-    public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
-        if (null == graphData)
-            return registerTestServices(TinkerGraph.open(getNumberIdManagerConfiguration())).traversal();
-        else if (graphData == LoadGraphWith.GraphData.CLASSIC)
-            return classic.traversal();
-        else if (graphData == LoadGraphWith.GraphData.CREW)
-            return crew.traversal();
-        else if (graphData == LoadGraphWith.GraphData.MODERN)
-            return modern.traversal();
-        else if (graphData == LoadGraphWith.GraphData.SINK)
-            return sink.traversal();
-        else if (graphData == LoadGraphWith.GraphData.GRATEFUL)
-            return grateful.traversal();
-        else
-            throw new UnsupportedOperationException("GraphData not supported: " + graphData.name());
-    }
-
-    @Override
-    public String changePathToDataFile(final String pathToFileFromGremlin) {
-        return ".." + File.separator + pathToFileFromGremlin;
-    }
-
-    private static Configuration getNumberIdManagerConfiguration() {
-        final Configuration conf = new BaseConfiguration();
-        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, TinkerGraph.DefaultIdManager.INTEGER.name());
-        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, TinkerGraph.DefaultIdManager.INTEGER.name());
-        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, TinkerGraph.DefaultIdManager.LONG.name());
-        return conf;
-    }
-
-    /**
-     * Enables the storing of {@code null} property values when testing.
-     */
-    public static class NullWorld extends TinkerGraphWorld {
-
-        @Override
-        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
-            if (graphData != null)
-                throw new UnsupportedOperationException("GraphData not supported: " + graphData.name());
-
-            final Configuration conf = TinkerGraphWorld.getNumberIdManagerConfiguration();
-            conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, true);
-            return TinkerGraph.open(conf).traversal();
-        }
-    }
-
-    /**
-     * Turns on {@link GraphComputer} when testing.
-     */
-    public static class ComputerWorld extends TinkerGraphWorld {
-        private static final Random RANDOM = TestHelper.RANDOM;
-
-        private static final List<String> TAGS_TO_IGNORE = Arrays.asList(
-                "@StepDrop",
-                "@StepInject",
-                "@StepV",
-                "@StepE",
-                "@GraphComputerVerificationOneBulk",
-                "@GraphComputerVerificationStrategyNotSupported",
-                "@GraphComputerVerificationMidVNotSupported",
-                "@GraphComputerVerificationInjectionNotSupported",
-                "@GraphComputerVerificationStarGraphExceeded",
-                "@GraphComputerVerificationReferenceOnly",
-                "@TinkerServiceRegistry");
-
-        @Override
-        public void beforeEachScenario(final Scenario scenario) {
-            final List<String> ignores = TAGS_TO_IGNORE.stream().filter(t -> scenario.getSourceTagNames().contains(t)).collect(Collectors.toList());
-            if (!ignores.isEmpty())
-                throw new AssumptionViolatedException(String.format("This scenario is not supported with GraphComputer: %s", ignores));
-        }
-
-        @Override
-        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
-            if (null == graphData)
-                throw new AssumptionViolatedException("GraphComputer does not support mutation");
-
-            return super.getGraphTraversalSource(graphData).withStrategies(VertexProgramStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
-                put(VertexProgramStrategy.WORKERS, Runtime.getRuntime().availableProcessors());
-                put(VertexProgramStrategy.GRAPH_COMPUTER, RANDOM.nextBoolean() ?
-                        GraphComputer.class.getCanonicalName() :
-                        TinkerGraphComputer.class.getCanonicalName());
-            }})));
-        }
-    }
-}
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphAllowNullFeatureTest.java
similarity index 81%
copy from tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java
copy to tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphAllowNullFeatureTest.java
index ed32a1c0f0..feef942a5e 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphAllowNullFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphAllowNullFeatureTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.tinkergraph;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
+import com.google.inject.Provides;
 import com.google.inject.Stage;
 import io.cucumber.guice.CucumberModules;
 import io.cucumber.junit.Cucumber;
@@ -32,11 +33,10 @@ import org.junit.runner.RunWith;
 @CucumberOptions(
         tags = "@AllowNullPropertyValues",
         glue = { "org.apache.tinkerpop.gremlin.features" },
-        objectFactory = TinkerGraphAllowNullFeatureTest.TinkerGraphGuiceFactory.class,
+        objectFactory = TinkerTransactionGraphAllowNullFeatureTest.TinkerGraphGuiceFactory.class,
         features = { "classpath:/org/apache/tinkerpop/gremlin/test/features" },
         plugin = {"progress", "junit:target/cucumber.xml"})
-public class TinkerGraphAllowNullFeatureTest {
-
+public class TinkerTransactionGraphAllowNullFeatureTest {
     public static class TinkerGraphGuiceFactory extends AbstractGuiceFactory {
         public TinkerGraphGuiceFactory() {
             super(Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), new ServiceModule()));
@@ -46,7 +46,12 @@ public class TinkerGraphAllowNullFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.NullWorld.class);
+            bind(World.class).to(TinkerWorld.NullWorld.class);
+        }
+
+        @Provides
+        static TinkerWorld.NullWorld provideNullWorld() {
+            return new TinkerWorld.NullWorld(new TinkerWorld.TinkerTransactionGraphWorld());
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphComputerFeatureTest.java
similarity index 81%
copy from tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java
copy to tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphComputerFeatureTest.java
index 8083e95808..e0efdb9249 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphComputerFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphComputerFeatureTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.tinkergraph;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
+import com.google.inject.Provides;
 import com.google.inject.Stage;
 import io.cucumber.guice.CucumberModules;
 import io.cucumber.junit.Cucumber;
@@ -32,11 +33,10 @@ import org.junit.runner.RunWith;
 @CucumberOptions(
         tags = "not @RemoteOnly and not @GraphComputerVerificationElementSupported",
         glue = { "org.apache.tinkerpop.gremlin.features" },
-        objectFactory = TinkerGraphComputerFeatureTest.TinkerGraphGuiceFactory.class,
+        objectFactory = TinkerTransactionGraphComputerFeatureTest.TinkerGraphGuiceFactory.class,
         features = { "classpath:/org/apache/tinkerpop/gremlin/test/features" },
         plugin = {"progress", "junit:target/cucumber.xml"})
-public class TinkerGraphComputerFeatureTest {
-
+public class TinkerTransactionGraphComputerFeatureTest {
     public static class TinkerGraphGuiceFactory extends AbstractGuiceFactory {
         public TinkerGraphGuiceFactory() {
             super(Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), new ServiceModule()));
@@ -46,7 +46,12 @@ public class TinkerGraphComputerFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.ComputerWorld.class);
+            bind(World.class).to(TinkerWorld.ComputerWorld.class);
+        }
+
+        @Provides
+        static TinkerWorld.ComputerWorld provideComputerWorld() {
+            return new TinkerWorld.ComputerWorld(new TinkerWorld.TinkerTransactionGraphWorld());
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphFeatureTest.java
similarity index 90%
copy from tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
copy to tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphFeatureTest.java
index da655d5437..1c3dba0bec 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphFeatureTest.java
@@ -32,10 +32,10 @@ import org.junit.runner.RunWith;
 @CucumberOptions(
         tags = "not @RemoteOnly and not @GraphComputerOnly and not @AllowNullPropertyValues",
         glue = { "org.apache.tinkerpop.gremlin.features" },
-        objectFactory = TinkerGraphFeatureTest.TinkerGraphGuiceFactory.class,
+        objectFactory = TinkerTransactionGraphFeatureTest.TinkerGraphGuiceFactory.class,
         features = { "classpath:/org/apache/tinkerpop/gremlin/test/features" },
         plugin = {"progress", "junit:target/cucumber.xml"})
-public class TinkerGraphFeatureTest {
+public class TinkerTransactionGraphFeatureTest {
 
     public static class TinkerGraphGuiceFactory extends AbstractGuiceFactory {
         public TinkerGraphGuiceFactory() {
@@ -46,7 +46,7 @@ public class TinkerGraphFeatureTest {
     public static final class ServiceModule extends AbstractModule {
         @Override
         protected void configure() {
-            bind(World.class).to(TinkerGraphWorld.class);
+            bind(World.class).to(TinkerWorld.TinkerTransactionGraphWorld.class);
         }
     }
 }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
new file mode 100644
index 0000000000..2fa1f2e2b2
--- /dev/null
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
@@ -0,0 +1,212 @@
+/*
+ * 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.tinkergraph;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.tinkerpop.gremlin.AbstractGraphProvider;
+import org.apache.tinkerpop.gremlin.LoadGraphWith;
+import org.apache.tinkerpop.gremlin.TestHelper;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.GraphTest;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.io.IoEdgeTest;
+import org.apache.tinkerpop.gremlin.structure.io.IoVertexTest;
+import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedGraphTest;
+import org.apache.tinkerpop.gremlin.structure.util.star.StarGraphTest;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph.DefaultIdManager;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerEdge;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerElement;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraphVariables;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerProperty;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertexProperty;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class TinkerTransactionGraphProvider extends AbstractGraphProvider {
+
+    private static final Set<Class> IMPLEMENTATION = new HashSet<Class>() {{
+        add(TinkerEdge.class);
+        add(TinkerElement.class);
+        add(TinkerTransactionGraph.class);
+        add(TinkerGraphVariables.class);
+        add(TinkerProperty.class);
+        add(TinkerVertex.class);
+        add(TinkerVertexProperty.class);
+    }};
+
+    @Override
+    public Map<String, Object> getBaseConfiguration(final String graphName, final Class<?> test, final String testMethodName,
+                                                    final LoadGraphWith.GraphData loadGraphWith) {
+        final DefaultIdManager idManager = selectIdMakerFromGraphData(loadGraphWith);
+        final String idMaker = (idManager.equals(DefaultIdManager.ANY) ? selectIdMakerFromTest(test, testMethodName) : idManager).name();
+        return new HashMap<String, Object>() {{
+            put(Graph.GRAPH, TinkerTransactionGraph.class.getName());
+            put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, idMaker);
+            put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, idMaker);
+            put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, idMaker);
+            if (requiresListCardinalityAsDefault(loadGraphWith, test, testMethodName))
+                put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.list.name());
+            if (requiresPersistence(test, testMethodName)) {
+                put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_GRAPH_FORMAT, "gryo");
+                put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_GRAPH_LOCATION,TestHelper.makeTestDataFile(test, "temp", testMethodName + ".kryo"));
+            }
+        }};
+    }
+
+    @Override
+    public void clear(final Graph graph, final Configuration configuration) throws Exception {
+        if (graph != null)
+            graph.close();
+
+        // in the even the graph is persisted we need to clean up
+        final String graphLocation = null != configuration ? configuration.getString(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null) : null;
+        if (graphLocation != null) {
+            final File f = new File(graphLocation);
+            f.delete();
+        }
+    }
+
+    @Override
+    public Set<Class> getImplementations() {
+        return IMPLEMENTATION;
+    }
+
+    /**
+     * Determines if a test requires TinkerGraph persistence to be configured with graph location and format.
+     */
+    protected static boolean requiresPersistence(final Class<?> test, final String testMethodName) {
+        return test == GraphTest.class && testMethodName.equals("shouldPersistDataOnClose");
+    }
+
+    /**
+     * Determines if a test requires a different cardinality as the default or not.
+     */
+    protected static boolean requiresListCardinalityAsDefault(final LoadGraphWith.GraphData loadGraphWith,
+                                                            final Class<?> test, final String testMethodName) {
+        return loadGraphWith == LoadGraphWith.GraphData.CREW
+                || (test == StarGraphTest.class && testMethodName.equals("shouldAttachWithCreateMethod"))
+                || (test == DetachedGraphTest.class && testMethodName.equals("testAttachableCreateMethod"));
+    }
+
+    /**
+     * Some tests require special configuration for TinkerGraph to properly configure the id manager.
+     */
+    protected DefaultIdManager selectIdMakerFromTest(final Class<?> test, final String testMethodName) {
+        if (test.equals(GraphTest.class)) {
+            final Set<String> testsThatNeedLongIdManager = new HashSet<String>(){{
+                add("shouldIterateVerticesWithNumericIdSupportUsingDoubleRepresentation");
+                add("shouldIterateVerticesWithNumericIdSupportUsingDoubleRepresentations");
+                add("shouldIterateVerticesWithNumericIdSupportUsingIntegerRepresentation");
+                add("shouldIterateVerticesWithNumericIdSupportUsingIntegerRepresentations");
+                add("shouldIterateVerticesWithNumericIdSupportUsingFloatRepresentation");
+                add("shouldIterateVerticesWithNumericIdSupportUsingFloatRepresentations");
+                add("shouldIterateVerticesWithNumericIdSupportUsingStringRepresentation");
+                add("shouldIterateVerticesWithNumericIdSupportUsingStringRepresentations");
+                add("shouldIterateEdgesWithNumericIdSupportUsingDoubleRepresentation");
+                add("shouldIterateEdgesWithNumericIdSupportUsingDoubleRepresentations");
+                add("shouldIterateEdgesWithNumericIdSupportUsingIntegerRepresentation");
+                add("shouldIterateEdgesWithNumericIdSupportUsingIntegerRepresentations");
+                add("shouldIterateEdgesWithNumericIdSupportUsingFloatRepresentation");
+                add("shouldIterateEdgesWithNumericIdSupportUsingFloatRepresentations");
+                add("shouldIterateEdgesWithNumericIdSupportUsingStringRepresentation");
+                add("shouldIterateEdgesWithNumericIdSupportUsingStringRepresentations");
+            }};
+
+            final Set<String> testsThatNeedUuidIdManager = new HashSet<String>(){{
+                add("shouldIterateVerticesWithUuidIdSupportUsingStringRepresentation");
+                add("shouldIterateVerticesWithUuidIdSupportUsingStringRepresentations");
+                add("shouldIterateEdgesWithUuidIdSupportUsingStringRepresentation");
+                add("shouldIterateEdgesWithUuidIdSupportUsingStringRepresentations");
+            }};
+
+            if (testsThatNeedLongIdManager.contains(testMethodName))
+                return DefaultIdManager.LONG;
+            else if (testsThatNeedUuidIdManager.contains(testMethodName))
+                return DefaultIdManager.UUID;
+        }  else if (test.equals(IoEdgeTest.class)) {
+            final Set<String> testsThatNeedLongIdManager = new HashSet<String>(){{
+                add("shouldReadWriteEdge[graphson-v1]");
+                add("shouldReadWriteDetachedEdgeAsReference[graphson-v1]");
+                add("shouldReadWriteDetachedEdge[graphson-v1]");
+                add("shouldReadWriteEdge[graphson-v2]");
+                add("shouldReadWriteDetachedEdgeAsReference[graphson-v2]");
+                add("shouldReadWriteDetachedEdge[graphson-v2]");
+            }};
+
+            if (testsThatNeedLongIdManager.contains(testMethodName))
+                return DefaultIdManager.LONG;
+        } else if (test.equals(IoVertexTest.class)) {
+            final Set<String> testsThatNeedLongIdManager = new HashSet<String>(){{
+                add("shouldReadWriteVertexWithBOTHEdges[graphson-v1]");
+                add("shouldReadWriteVertexWithINEdges[graphson-v1]");
+                add("shouldReadWriteVertexWithOUTEdges[graphson-v1]");
+                add("shouldReadWriteVertexNoEdges[graphson-v1]");
+                add("shouldReadWriteDetachedVertexNoEdges[graphson-v1]");
+                add("shouldReadWriteDetachedVertexAsReferenceNoEdges[graphson-v1]");
+                add("shouldReadWriteVertexMultiPropsNoEdges[graphson-v1]");
+                add("shouldReadWriteVertexWithBOTHEdges[graphson-v2]");
+                add("shouldReadWriteVertexWithINEdges[graphson-v2]");
+                add("shouldReadWriteVertexWithOUTEdges[graphson-v2]");
+                add("shouldReadWriteVertexNoEdges[graphson-v2]");
+                add("shouldReadWriteDetachedVertexNoEdges[graphson-v2]");
+                add("shouldReadWriteDetachedVertexAsReferenceNoEdges[graphson-v2]");
+                add("shouldReadWriteVertexMultiPropsNoEdges[graphson-v2]");
+            }};
+
+            if (testsThatNeedLongIdManager.contains(testMethodName))
+                return DefaultIdManager.LONG;
+        }
+
+        return DefaultIdManager.ANY;
+    }
+
+    /**
+     * Test that load with specific graph data can be configured with a specific id manager as the data type to
+     * be used in the test for that graph is known.
+     */
+    protected DefaultIdManager selectIdMakerFromGraphData(final LoadGraphWith.GraphData loadGraphWith) {
+        if (null == loadGraphWith) return DefaultIdManager.ANY;
+        if (loadGraphWith.equals(LoadGraphWith.GraphData.CLASSIC))
+            return DefaultIdManager.INTEGER;
+        else if (loadGraphWith.equals(LoadGraphWith.GraphData.MODERN))
+            return DefaultIdManager.INTEGER;
+        else if (loadGraphWith.equals(LoadGraphWith.GraphData.CREW))
+            return DefaultIdManager.INTEGER;
+        else if (loadGraphWith.equals(LoadGraphWith.GraphData.GRATEFUL))
+            return DefaultIdManager.INTEGER;
+        else if (loadGraphWith.equals(LoadGraphWith.GraphData.SINK))
+            return DefaultIdManager.INTEGER;
+        else
+            throw new IllegalStateException(String.format("Need to define a new %s for %s", TinkerTransactionGraph.IdManager.class.getName(), loadGraphWith.name()));
+    }
+
+    @Override
+    protected void readIntoGraph(final Graph graph, final String path) throws IOException {
+        super.readIntoGraph(graph, path);
+
+        graph.tx().commit();
+    }
+}
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java
new file mode 100644
index 0000000000..b0abbdef75
--- /dev/null
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java
@@ -0,0 +1,254 @@
+/*
+ * 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.tinkergraph;
+
+import io.cucumber.java.Scenario;
+import org.apache.commons.configuration2.BaseConfiguration;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.MapConfiguration;
+import org.apache.tinkerpop.gremlin.LoadGraphWith;
+import org.apache.tinkerpop.gremlin.TestHelper;
+import org.apache.tinkerpop.gremlin.features.World;
+import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
+import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer;
+import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerDegreeCentralityFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerTextSearchFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerTransactionGraph;
+import org.junit.AssumptionViolatedException;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/**
+ * The abstract {@link World} implementation for AbstractTinkerGraph.
+ */
+public abstract class TinkerWorld implements World {
+    @Override
+    public String changePathToDataFile(final String pathToFileFromGremlin) {
+        return ".." + File.separator + pathToFileFromGremlin;
+    }
+
+    /**
+     * Get an instance of the underlying AbstractTinkerGraph with the provided configuration.
+     */
+    public abstract AbstractTinkerGraph open(final Configuration configuration);
+
+    protected static Configuration getNumberIdManagerConfiguration() {
+        final Configuration conf = new BaseConfiguration();
+        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, AbstractTinkerGraph.DefaultIdManager.INTEGER.name());
+        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, AbstractTinkerGraph.DefaultIdManager.INTEGER.name());
+        conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, AbstractTinkerGraph.DefaultIdManager.LONG.name());
+        return conf;
+    }
+
+    protected static AbstractTinkerGraph registerTestServices(final AbstractTinkerGraph graph) {
+        graph.getServiceRegistry().registerService(new TinkerTextSearchFactory(graph));
+        graph.getServiceRegistry().registerService(new TinkerDegreeCentralityFactory(graph));
+        return graph;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * The concrete {@link World} implementation for TinkerGraph that provides the {@link GraphTraversalSource}
+     * instances required by the Gherkin test suite.
+     */
+    public static class TinkerGraphWorld extends TinkerWorld {
+        private static final AbstractTinkerGraph modern = registerTestServices(TinkerFactory.createModern());
+        private static final AbstractTinkerGraph classic = registerTestServices(TinkerFactory.createClassic());
+        private static final AbstractTinkerGraph crew = registerTestServices(TinkerFactory.createTheCrew());
+        private static final AbstractTinkerGraph sink = registerTestServices(TinkerFactory.createKitchenSink());
+        private static final AbstractTinkerGraph grateful = registerTestServices(TinkerFactory.createGratefulDead());
+
+        @Override
+        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
+            if (null == graphData)
+                return registerTestServices(TinkerGraph.open(getNumberIdManagerConfiguration())).traversal();
+            else if (graphData == LoadGraphWith.GraphData.CLASSIC)
+                return classic.traversal();
+            else if (graphData == LoadGraphWith.GraphData.CREW)
+                return crew.traversal();
+            else if (graphData == LoadGraphWith.GraphData.MODERN)
+                return modern.traversal();
+            else if (graphData == LoadGraphWith.GraphData.SINK)
+                return sink.traversal();
+            else if (graphData == LoadGraphWith.GraphData.GRATEFUL)
+                return grateful.traversal();
+            else
+                throw new UnsupportedOperationException("GraphData not supported: " + graphData.name());
+        }
+
+        @Override
+        public AbstractTinkerGraph open(final Configuration configuration) {
+            return TinkerGraph.open(configuration);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * The {@link World} implementation for TinkerTransactionGraph that provides the {@link GraphTraversalSource}
+     * instances required by the Gherkin test suite.
+     */
+    public static class TinkerTransactionGraphWorld extends TinkerWorld {
+        private static final AbstractTinkerGraph modern = registerTestServices(TinkerFactory.createTxModern());
+        private static final AbstractTinkerGraph classic = registerTestServices(TinkerFactory.createTxClassic());
+        private static final AbstractTinkerGraph crew = registerTestServices(TinkerFactory.createTxTheCrew());
+        private static final AbstractTinkerGraph sink = registerTestServices(TinkerFactory.createTxKitchenSink());
+        private static final AbstractTinkerGraph grateful = registerTestServices(TinkerFactory.createTxGratefulDead());
+
+        @Override
+        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
+            if (null == graphData)
+                return registerTestServices(TinkerTransactionGraph.open(getNumberIdManagerConfiguration())).traversal();
+            else if (graphData == LoadGraphWith.GraphData.CLASSIC)
+                return classic.traversal();
+            else if (graphData == LoadGraphWith.GraphData.CREW)
+                return crew.traversal();
+            else if (graphData == LoadGraphWith.GraphData.MODERN)
+                return modern.traversal();
+            else if (graphData == LoadGraphWith.GraphData.SINK)
+                return sink.traversal();
+            else if (graphData == LoadGraphWith.GraphData.GRATEFUL)
+                return grateful.traversal();
+            else
+                throw new UnsupportedOperationException("GraphData not supported: " + graphData.name());
+        }
+
+        @Override
+        public AbstractTinkerGraph open(final Configuration configuration) {
+            return TinkerTransactionGraph.open(configuration);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Enables the storing of {@code null} property values when testing. This is a terminal decorator and shouldn't be
+     * used as an input into another decorator.
+     */
+    public static class NullWorld implements World {
+        private TinkerWorld world;
+
+        public NullWorld(TinkerWorld world) { this.world = world; }
+
+        @Override
+        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
+            if (graphData != null)
+                throw new UnsupportedOperationException("GraphData not supported: " + graphData.name());
+
+            final Configuration conf = getNumberIdManagerConfiguration();
+            conf.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, true);
+            return world.open(conf).traversal();
+        }
+
+        @Override
+        public void beforeEachScenario(final Scenario scenario) {
+            this.world.beforeEachScenario(scenario);
+        }
+
+        @Override
+        public void afterEachScenario() {
+            this.world.afterEachScenario();
+        }
+
+        @Override
+        public String changePathToDataFile(final String pathToFileFromGremlin) {
+            return this.world.changePathToDataFile(pathToFileFromGremlin);
+        }
+
+        @Override
+        public String convertIdToScript(final Object id, final Class<? extends Element> type) {
+            return this.world.convertIdToScript(id, type);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Enables testing of GraphComputer functionality.
+     */
+    public static class ComputerWorld implements World {
+        private static final Random RANDOM = TestHelper.RANDOM;
+
+        private static final List<String> TAGS_TO_IGNORE = Arrays.asList(
+                "@StepDrop",
+                "@StepInject",
+                "@StepV",
+                "@StepE",
+                "@GraphComputerVerificationOneBulk",
+                "@GraphComputerVerificationStrategyNotSupported",
+                "@GraphComputerVerificationMidVNotSupported",
+                "@GraphComputerVerificationInjectionNotSupported",
+                "@GraphComputerVerificationStarGraphExceeded",
+                "@GraphComputerVerificationReferenceOnly",
+                "@TinkerServiceRegistry");
+
+        private World world;
+
+        public ComputerWorld(World world) { this.world = world; }
+
+        @Override
+        public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) {
+            if (null == graphData)
+                throw new AssumptionViolatedException("GraphComputer does not support mutation");
+
+            return this.world.getGraphTraversalSource(graphData).withStrategies(VertexProgramStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
+                put(VertexProgramStrategy.WORKERS, Runtime.getRuntime().availableProcessors());
+                put(VertexProgramStrategy.GRAPH_COMPUTER, RANDOM.nextBoolean() ?
+                        GraphComputer.class.getCanonicalName() :
+                        TinkerGraphComputer.class.getCanonicalName());
+            }})));
+        }
+
+        @Override
+        public void beforeEachScenario(final Scenario scenario) {
+            final List<String> ignores = TAGS_TO_IGNORE.stream().filter(t -> scenario.getSourceTagNames().contains(t)).collect(Collectors.toList());
+            if (!ignores.isEmpty())
+                throw new AssumptionViolatedException(String.format("This scenario is not supported with GraphComputer: %s", ignores));
+
+            this.world.beforeEachScenario(scenario);
+        }
+
+        @Override
+        public void afterEachScenario() {
+            this.world.afterEachScenario();
+        }
+
+        @Override
+        public String changePathToDataFile(final String pathToFileFromGremlin) {
+            return this.world.changePathToDataFile(pathToFileFromGremlin);
+        }
+
+        @Override
+        public String convertIdToScript(final Object id, final Class<? extends Element> type) {
+            return this.world.convertIdToScript(id, type);
+        }
+    }
+}
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphServiceTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphServiceTest.java
index 66cb9c7d70..2901ef76da 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphServiceTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphServiceTest.java
@@ -54,7 +54,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select
 import static org.apache.tinkerpop.gremlin.structure.service.Service.ServiceCallContext;
 import static org.apache.tinkerpop.gremlin.structure.service.Service.Type;
 import static org.apache.tinkerpop.gremlin.tinkergraph.services.TinkerServiceRegistry.LambdaServiceFactory;
-import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asMap;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphStructureStandardTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphStructureStandardTest.java
new file mode 100644
index 0000000000..d4ab2152ab
--- /dev/null
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphStructureStandardTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.GraphProviderClass;
+import org.apache.tinkerpop.gremlin.structure.StructureStandardSuite;
+import org.apache.tinkerpop.gremlin.tinkergraph.TinkerTransactionGraphProvider;
+import org.junit.runner.RunWith;
+
+/**
+ * Executes the Standard Gremlin Structure Test Suite using TinkerTransactionGraph.
+ */
+@RunWith(StructureStandardSuite.class)
+@GraphProviderClass(provider = TinkerTransactionGraphProvider.class, graph = TinkerTransactionGraph.class)
+public class TinkerTransactionGraphStructureStandardTest {
+
+}
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
new file mode 100644
index 0000000000..3bc8874fb5
--- /dev/null
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
@@ -0,0 +1,734 @@
+/*
+ * 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.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.util.TransactionException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class TinkerTransactionGraphTest {
+
+    final Object vid = 100;
+
+    ///// vertex tests
+
+    @Test
+    public void shouldReturnSameVertexInstanceInsideTransaction() {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final Vertex vertex = gtx.addV().property(T.id, vid).next();
+        assertEquals(vertex, gtx.V().next());
+        assertEquals(vertex, gtx.V(vid).next());
+        assertEquals(vertex, gtx.V().toList().get(0));
+    }
+
+    @Test
+    public void shouldDeleteVertexOnCommit() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().iterate();
+        gtx.tx().commit();
+
+        assertEquals(1, (long) gtx.V().count().next());
+
+        gtx.V().drop().iterate();
+        assertEquals(0, (long) gtx.V().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+
+        gtx.tx().commit();
+
+        assertEquals(0, (long) gtx.V().count().next());
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+    }
+
+    @Test
+    public void shouldDeleteCreatedVertexOnCommit() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        // create and drop Vertex in same transaction
+        gtx.addV().property(T.id, vid).iterate();
+        gtx.V(vid).drop().iterate();
+
+        assertEquals(0, (long) gtx.V().count().next());
+        countElementsInNewThreadTx(g, 0, 0);
+
+        gtx.tx().commit();
+
+        assertEquals(0, (long) gtx.V().count().next());
+        countElementsInNewThreadTx(g, 0, 0);
+        // should remove unused container
+        assertEquals(0, g.getVertices().size());
+    }
+
+
+    @Test
+    public void shouldDeleteUnusedVertexContainerOnCommit() {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).iterate();
+        gtx.tx().commit();
+
+        gtx.V(vid).drop().iterate();
+        gtx.tx().commit();
+
+        // should remove unused container
+        assertEquals(0, g.getVertices().size());
+    }
+
+    @Test
+    public void shouldDeleteUnusedVertexContainerOnRollback() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).iterate();
+        gtx.tx().commit();
+
+        gtx.V(vid).properties("test-property").iterate();
+        // 1 container for existing vertex
+        assertEquals(1, g.getVertices().size());
+
+        // other tx try to add new vertices and modify existing
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+
+            gtx2.addV().addV().iterate();
+            gtx.V(vid).properties("test-property").iterate();
+
+            // 1 container for existing vertex + 2 for new ones
+            assertEquals(3, g.getVertices().size());
+
+            assertEquals(3, (long) gtx2.V().count().next());
+
+            gtx2.tx().rollback();
+            // containers for new vertices should be removed
+            assertEquals(1, g.getVertices().size());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().rollback();
+
+        // should keep container because vertex not created in this transaction
+        assertEquals(1, g.getVertices().size());
+    }
+
+    @Test
+    public void shouldDeleteUnusedVertexContainerOnConcurrentVertexDelete() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        gtx.tx().commit();
+
+        gtx.V(v1.id()).drop().iterate();
+
+        // other tx try to remove same vertex
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+
+            gtx2.V(v1.id()).drop().iterate();
+
+            // should be ok
+            assertEquals(0, (long) gtx2.V().count().next());
+            assertEquals(0, (long) gtx2.E().count().next());
+
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        // try do delete in initial tx
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        // should remove unused container
+        assertEquals(0, g.getVertices().size());
+    }
+
+    ///// Edge tests
+
+    @Test
+    public void shouldRemoveContainerAndReferenceFromVertexOnDeleteEdge() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        gtx.E().hasLabel("tests").drop().iterate();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // test count in other thread transaction
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(2L, (long) gtx2.V().count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // should remove reference from edge to parent vertices
+        final TinkerVertex v1afterTx = g.getVertices().get(v1.id()).get();
+        final TinkerVertex v2afterTx = g.getVertices().get(v2.id()).get();
+        assertNull(v1afterTx.inEdgesId);
+        assertEquals(0, v1afterTx.outEdgesId.get("tests").size());
+        assertEquals(0, v2afterTx.inEdgesId.get("tests").size());
+        assertNull(v2afterTx.outEdgesId);
+
+        // should remove unused container
+        assertEquals(0, g.getEdges().size());
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    @Test
+    public void shouldRemoveContainerAndReferenceFromVertexOnConcurrentDeleteEdge() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        gtx.addE("tests").from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(1, (long) gtx.E().count().next());
+
+        gtx.E().hasLabel("tests").drop().iterate();
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // other tx try to remove same edge
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(2L, (long) gtx2.V().count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+
+            // try to remove edge
+            gtx2.E().hasLabel("tests").drop().iterate();
+
+            // should be ok
+            assertEquals(2, (long) gtx2.V().count().next());
+            assertEquals(0, (long) gtx2.E().count().next());
+
+            gtx2.tx().commit();
+        });
+        thread.start();
+        thread.join();
+
+        // try do delete in initial tx
+        try {
+            gtx.tx().commit();
+            fail("should throw TransactionException");
+        } catch (TransactionException e) {
+
+        }
+
+        // should remove unused container
+        assertEquals(0, g.getEdges().size());
+
+        assertEquals(2, (long) gtx.V().count().next());
+        assertEquals(0, (long) gtx.E().count().next());
+
+        // should remove reference from edge to parent vertices
+        final TinkerVertex v1afterTx = g.getVertices().get(v1.id()).get();
+        final TinkerVertex v2afterTx = g.getVertices().get(v2.id()).get();
+        assertNull(v1afterTx.inEdgesId);
+        assertEquals(0, v1afterTx.outEdgesId.get("tests").size());
+        assertEquals(0, v2afterTx.inEdgesId.get("tests").size());
+        assertNull(v2afterTx.outEdgesId);
+
+        countElementsInNewThreadTx(g, 2, 0);
+    }
+
+    // index tests for vertex
+
+    @Test
+    public void shouldCreateIndexForNewVertex() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Vertex.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).property("test-property", 1).iterate();
+        gtx.addV().property("test-property", 2).iterate();
+
+        assertEquals(1L, (long) gtx.V().has("test-property", 1).count().next());
+        assertEquals(vid, gtx.V().has("test-property", 1).id().next());
+        assertEquals(0L, (long) gtx.V().has("test-property", 3).count().next());
+        assertEquals(2L, (long) gtx.V().count().next());
+
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(0L, (long) gtx2.V().has("test-property", 1).count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(1L, (long) gtx3.V().has("test-property", 1).count().next());
+        assertEquals(vid, gtx3.V().has("test-property", 1).id().next());
+        assertEquals(0L, (long) gtx3.V().has("test-property", 3).count().next());
+        assertEquals(2L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.vertexIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(1, index.get(1).size());
+        assertEquals(vid, index.get(1).iterator().next().get().id());
+    }
+
+    @Test
+    public void shouldCreateIndexForNullVertexProperty() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Vertex.class);
+        g.allowNullPropertyValues = true;
+        final Integer nullValue = null;
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).property("test-property", nullValue).iterate();
+        gtx.addV().property("test-property", 2).iterate();
+
+        assertEquals(1L, (long) gtx.V().has("test-property", nullValue).count().next());
+        assertEquals(vid, gtx.V().has("test-property", nullValue).id().next());
+        assertEquals(0L, (long) gtx.V().has("test-property", 1).count().next());
+        assertEquals(2L, (long) gtx.V().count().next());
+
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(0L, (long) gtx2.V().has("test-property", nullValue).count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(1L, (long) gtx3.V().has("test-property", nullValue).count().next());
+        assertEquals(vid, gtx3.V().has("test-property", nullValue).id().next());
+        assertEquals(0L, (long) gtx3.V().has("test-property", 1).count().next());
+        assertEquals(2L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.vertexIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(1, index.get(AbstractTinkerIndex.IndexedNull.instance()).size());
+        assertEquals(vid, index.get(AbstractTinkerIndex.IndexedNull.instance()).iterator().next().get().id());
+    }
+
+    @Test
+    public void shouldRemoveIndexForRemovedVertex() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Vertex.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).property("test-property", 1).iterate();
+
+        gtx.tx().commit();
+
+        gtx.V(vid).drop().iterate();
+
+        assertEquals(0L, (long) gtx.V().has("test-property", 1).count().next());
+        assertEquals(0L, (long) gtx.V().count().next());
+
+        // in another thread elements exists before commit
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(1L, (long) gtx2.V().has("test-property", 1).count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().has("test-property", 1).count().next());
+        assertEquals(0L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 0, 0);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.vertexIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(0, index.size());
+     }
+
+    @Test
+    public void shouldRemoveIndexForRemovedVertexProperty() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Vertex.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        gtx.addV().property(T.id, vid).property("test-property", 1).iterate();
+
+        gtx.tx().commit();
+
+        gtx.V(vid).properties("test-property").drop().iterate();
+
+        assertEquals(0L, (long) gtx.V().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.V().count().next());
+
+        // in another thread elements exists before commit
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(1L, (long) gtx2.V().has("test-property", 1).count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.V().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx3.V().count().next());
+
+        countElementsInNewThreadTx(g, 1, 0);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.vertexIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(0, index.size());
+    }
+
+    // index tests for edge
+
+    @Test
+    public void shouldCreateIndexForNewEdge() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Edge.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        final TinkerEdge edge = (TinkerEdge) gtx.addE("tests").property("test-property", 1).from(v1).to(v2).next();
+
+        assertEquals(1L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        // in another thread index not updated yet
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(0L, (long) gtx2.E().has("test-property", 1).count().next());
+            assertEquals(0L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(1L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 1);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.edgeIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(1, index.size());
+        assertEquals(1, index.get(1).size());
+        assertEquals(edge.id(), index.get(1).iterator().next().get().id());
+    }
+
+    @Test
+    public void shouldChangeIndexForChangedEdge() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Edge.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        final TinkerEdge edge = (TinkerEdge) gtx.addE("tests").property("test-property", 1).from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        gtx.E(edge.id()).property("test-property", 2).iterate();
+
+        assertEquals(0L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.E().has("test-property", 2).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        // in another thread index not updated yet
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(1L, (long) gtx2.E().has("test-property", 1).count().next());
+            assertEquals(0L, (long) gtx2.E().has("test-property", 2).count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.E().has("test-property", 2).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 1);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.edgeIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(1, index.size());
+        assertNull(index.get(1));
+        assertEquals(1, index.get(2).size());
+        assertEquals(edge.id(), index.get(2).iterator().next().get().id());
+    }
+
+    @Test
+    public void shouldRemoveIndexForRemovedEdge() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Edge.class);
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        final TinkerEdge edge = (TinkerEdge) gtx.addE("tests").property("test-property", 1).from(v1).to(v2).next();
+        gtx.tx().commit();
+
+        gtx.E(edge.id()).drop().iterate();
+
+        assertEquals(0L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(0L, (long) gtx.E().count().next());
+
+        // in another thread elements exists before commit
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(1L, (long) gtx2.E().has("test-property", 1).count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.E().has("test-property", 1).count().next());
+        assertEquals(0L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 0);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.edgeIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(0, index.size());
+    }
+
+    @Test
+    public void shouldCreateIndexForNullProperty() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Edge.class);
+        g.allowNullPropertyValues = true;
+        final Integer nullValue = null;
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        final TinkerEdge edge = (TinkerEdge) gtx.addE("tests").property("test-property", nullValue).
+                from(v1).to(v2).next();
+
+        assertEquals(1L, (long) gtx.E().has("test-property", nullValue).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        // in another thread index not updated yet
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(0L, (long) gtx2.E().has("test-property", nullValue).count().next());
+            assertEquals(0L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(1L, (long) gtx3.E().has("test-property", nullValue).count().next());
+        assertEquals(1L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 1);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.edgeIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(1, index.size());
+        assertEquals(1, index.get(AbstractTinkerIndex.IndexedNull.instance()).size());
+        assertEquals(edge.id(), index.get(AbstractTinkerIndex.IndexedNull.instance()).iterator().next().get().id());
+    }
+
+    @Test
+    public void shouldRemoveIndexForNullProperty() throws InterruptedException {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        g.createIndex("test-property", Edge.class);
+        final Integer nullValue = null;
+
+        final GraphTraversalSource gtx = g.tx().begin();
+
+        // create initial edge with indexed property
+        final TinkerVertex v1 = (TinkerVertex) gtx.addV().next();
+        final TinkerVertex v2 = (TinkerVertex) gtx.addV().next();
+        final TinkerEdge edge = (TinkerEdge) gtx.addE("tests").property("test-property", 1).
+                from(v1).to(v2).next();
+
+        gtx.tx().commit();
+
+        // remove property from index
+        gtx.E(edge.id()).property("test-property", nullValue).iterate();
+
+        assertEquals(0L, (long) gtx.E().has("test-property", 1).count().next());
+        assertEquals(1L, (long) gtx.E().count().next());
+
+        // in another thread index not updated yet
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx2 = g.tx().begin();
+            assertEquals(1L, (long) gtx2.E().has("test-property", 1).count().next());
+            assertEquals(1L, (long) gtx2.E().count().next());
+        });
+        thread.start();
+        thread.join();
+
+        gtx.tx().commit();
+
+        final GraphTraversalSource gtx3 = g.tx().begin();
+        assertEquals(0L, (long) gtx3.E().has("test-property", nullValue).count().next());
+        assertEquals(1L, (long) gtx3.E().count().next());
+
+        countElementsInNewThreadTx(g, 2, 1);
+
+        final Map<Object, Set<TinkerElementContainer<?>>> index =
+                (Map<Object, Set<TinkerElementContainer<?>>>) ((TinkerTransactionalIndex) g.edgeIndex).index.get("test-property");
+        assertNotNull(index);
+        // should be only vertex vid in set
+        assertEquals(0, index.size());
+    }
+
+    // tests for cloning elements
+
+    @Test
+    public void vertexCloneTest() {
+        final TinkerVertex vertex = new TinkerVertex(123, "label", TinkerTransactionGraph.open());
+        final TinkerVertexProperty vp = new TinkerVertexProperty(vertex, "test", "qq");
+        final TinkerEdge edge = new TinkerEdge(1, vertex, "label", vertex);
+        vertex.properties = new ConcurrentHashMap<>();
+        vertex.properties.put("test", new ArrayList<>());
+        vertex.properties.get("test").add(vp);
+        vertex.inEdgesId = new ConcurrentHashMap<>();
+        vertex.inEdgesId.put("label", ConcurrentHashMap.newKeySet());
+        vertex.inEdgesId.get("label").add(edge);
+
+        final TinkerVertex copy = (TinkerVertex) vertex.clone();
+        vertex.properties.get("test").remove(vp);
+        assertEquals(1, copy.properties.get("test").size());
+        vertex.inEdgesId.get("label").remove(edge);
+        assertEquals(1, copy.inEdgesId.get("label").size());
+    }
+
+    @Test
+    public void edgeCloneTest() {
+        final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+        final TinkerVertex v1 = (TinkerVertex) g.addVertex();
+        final TinkerVertex v2 = (TinkerVertex) g.addVertex();
+        final TinkerEdge edge = new TinkerEdge(3, v1, "label", v2);
+        final TinkerProperty property = new TinkerProperty(edge, "test", "qq");
+        edge.properties = new ConcurrentHashMap<>();
+        edge.properties.put(property.key(), property);
+
+        final TinkerEdge copy = (TinkerEdge) edge.clone();
+        edge.properties.remove(property.key());
+        assertEquals(1, copy.properties.size());
+    }
+
+    // utility methods
+
+    private void countElementsInNewThreadTx(final TinkerTransactionGraph g, final long verticesCount, final long edgesCount) throws InterruptedException {
+        final Thread thread = new Thread(() -> {
+            final GraphTraversalSource gtx = g.tx().begin();
+            assertEquals(verticesCount, (long) gtx.V().count().next());
+            assertEquals(edgesCount, (long) gtx.E().count().next());
+        });
+        thread.start();
+        thread.join();
+    }
+}
diff --git a/tinkergraph-gremlin/src/test/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory b/tinkergraph-gremlin/src/test/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory
index 54484d454d..9eb15f9a66 100644
--- a/tinkergraph-gremlin/src/test/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory
+++ b/tinkergraph-gremlin/src/test/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory
@@ -1,3 +1,6 @@
 org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphFeatureTest$TinkerGraphGuiceFactory
 org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphComputerFeatureTest$TinkerGraphGuiceFactory
-org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphAllowNullFeatureTest$TinkerGraphGuiceFactory
\ No newline at end of file
+org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphAllowNullFeatureTest$TinkerGraphGuiceFactory
+org.apache.tinkerpop.gremlin.tinkergraph.TinkerTransactionGraphFeatureTest$TinkerGraphGuiceFactory
+org.apache.tinkerpop.gremlin.tinkergraph.TinkerTransactionGraphComputerFeatureTest$TinkerGraphGuiceFactory
+org.apache.tinkerpop.gremlin.tinkergraph.TinkerTransactionGraphAllowNullFeatureTest$TinkerGraphGuiceFactory
\ No newline at end of file