You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2021/12/01 17:40:04 UTC

[tinkerpop] branch TINKERPOP-2635 updated (04884ec -> 373c5d0)

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

spmallette pushed a change to branch TINKERPOP-2635
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


 discard 04884ec  TINKERPOP-2635 Add fail() step
     add 3baa84f  TINKERPOP-2656 update python translator
     add b138c20  Merge branch 'pr-1506' into 3.5-dev
     add 736d0c9  Merge branch '3.5-dev'
     add 7d6a2b1  TINKERPOP-2657 Removed GroovyTranslator from gremlin-groovy
     add 55e2340  Merge pull request #1505 from apache/TINKERPOP-2657
     add 5d19808  TINKERPOP-2659 Bumped to node v16
     add 1f1c537  Merge pull request #1508 from apache/TINKERPOP-2659
     add 55cef8c  Added ProductiveByStrategy grammar handling
     add 30772d2  Merge branch '3.5-dev'
     new 373c5d0  TINKERPOP-2635 Add fail() step

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (04884ec)
            \
             N -- N -- N   refs/heads/TINKERPOP-2635 (373c5d0)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

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


Summary of changes:
 CHANGELOG.asciidoc                                 |    3 +
 .../dev/developer/development-environment.asciidoc |    2 +
 docs/src/dev/developer/for-committers.asciidoc     |   12 +
 docs/src/dev/developer/release.asciidoc            |    1 +
 docs/src/reference/the-traversal.asciidoc          |   29 +-
 docs/src/upgrade/release-3.6.x.asciidoc            |    8 +
 .../language/grammar/TraversalStrategyVisitor.java |   11 +
 .../traversal/translator/PythonTranslator.java     |   50 +
 .../grammar/TraversalStrategyVisitorTest.java      |   10 +-
 .../traversal/translator/PythonTranslatorTest.java |   16 +
 .../Gherkin/CommonSteps.cs                         |   14 +
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |    2 +
 .../groovy/jsr223/GremlinGroovyScriptEngine.java   |    5 +-
 .../gremlin/groovy/jsr223/GroovyTranslator.java    |  376 ---
 .../groovy/jsr223/GroovyTranslatorTest.java        |  429 ---
 .../ParameterizedGroovyTranslatorProvider.java     |    1 +
 .../jsr223/ParameterizedGroovyTranslatorTest.java  |  664 ----
 gremlin-javascript/pom.xml                         |    6 +-
 .../gremlin-javascript/package-lock.json           | 3240 +++++++++++++++++++-
 .../test/cucumber/feature-steps.js                 |    6 +
 .../test/integration/traversal-test.js             |   17 +-
 .../gremlin-javascript/test/unit/client-test.js    |    6 +-
 .../src/main/python/radish/feature_steps.py        |    6 +
 .../driver/remote/AbstractRemoteGraphProvider.java |    2 +-
 ...emoteGraphGroovyTranslatorComputerProvider.java |  156 -
 .../GryoRemoteGraphGroovyTranslatorProvider.java   |   45 -
 ...teGraphGroovyTranslatorProcessComputerTest.java |   33 -
 ...teGraphGroovyTranslatorProcessStandardTest.java |   33 -
 .../tinkerpop/gremlin/features/StepDefinition.java |   20 +
 29 files changed, 3442 insertions(+), 1761 deletions(-)
 delete mode 100644 gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
 delete mode 100644 gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorTest.java
 delete mode 100644 gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ParameterizedGroovyTranslatorTest.java
 delete mode 100644 gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorComputerProvider.java
 delete mode 100644 gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorProvider.java
 delete mode 100644 gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/RemoteGraphGroovyTranslatorProcessComputerTest.java
 delete mode 100644 gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/RemoteGraphGroovyTranslatorProcessStandardTest.java

[tinkerpop] 01/01: TINKERPOP-2635 Add fail() step

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

spmallette pushed a commit to branch TINKERPOP-2635
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 373c5d00a1ed826fc53abdfc12a55f0ac265d70a
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Nov 30 13:57:30 2021 -0500

    TINKERPOP-2635 Add fail() step
---
 CHANGELOG.asciidoc                                 |   2 +
 docs/src/dev/developer/for-committers.asciidoc     |   1 +
 docs/src/reference/the-traversal.asciidoc          |  37 ++++++
 docs/src/upgrade/release-3.6.x.asciidoc            |  23 +++-
 .../tinkerpop/gremlin/console/Console.groovy       | 124 +++++++++++++++------
 .../language/grammar/GremlinBaseVisitor.java       |  13 +++
 .../language/grammar/TraversalMethodVisitor.java   |  17 +++
 .../gremlin/process/traversal/Failure.java         | 101 +++++++++++++++++
 .../gremlin/process/traversal/Traverser.java       |   9 ++
 .../traversal/dsl/graph/GraphTraversal.java        |  54 +++++++--
 .../gremlin/process/traversal/dsl/graph/__.java    |  14 +++
 .../traversal/step/sideEffect/FailStep.java        |  87 +++++++++++++++
 .../traverser/B_LP_NL_O_S_SE_SL_Traverser.java     |   5 +
 .../traverser/B_LP_O_S_SE_SL_Traverser.java        |   1 -
 .../traverser/B_NL_O_S_SE_SL_Traverser.java        |   1 -
 .../traversal/traverser/B_O_S_SE_SL_Traverser.java |   7 ++
 .../traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java  |   6 +
 .../traverser/LP_NL_O_OB_S_SE_SL_Traverser.java    |   6 +
 .../traverser/LP_O_OB_S_SE_SL_Traverser.java       |   1 -
 .../traverser/NL_O_OB_S_SE_SL_Traverser.java       |   6 +
 .../traverser/O_OB_S_SE_SL_Traverser.java          |   9 +-
 .../Process/Traversal/GraphTraversal.cs            |  27 +++++
 .../src/Gremlin.Net/Process/Traversal/__.cs        |  16 +++
 .../Gherkin/CommonSteps.cs                         |  67 ++++++++---
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   5 +
 .../apache/tinkerpop/gremlin/driver/Tokens.java    |  15 +++
 .../gremlin/driver/message/ResponseStatusCode.java |  11 ++
 .../ast/VarAsBindingASTTransformation.groovy       |   3 +
 .../lib/process/graph-traversal.js                 |  11 ++
 .../test/cucumber/feature-steps.js                 |  14 ++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   3 +
 gremlin-language/src/main/antlr4/Gremlin.g4        |   6 +
 .../gremlin_python/process/graph_traversal.py      |  14 +++
 .../src/main/python/radish/feature_steps.py        |  24 +++-
 gremlin-python/src/main/python/radish/gremlin.py   |   3 +
 .../gremlin/server/handler/AbstractSession.java    |  31 ++++--
 .../gremlin/server/op/AbstractEvalOpProcessor.java |  25 +++--
 .../gremlin/server/op/AbstractOpProcessor.java     |   9 +-
 .../server/op/session/SessionOpProcessor.java      |  67 ++++++++---
 .../server/op/traversal/TraversalOpProcessor.java  |  47 +++++---
 .../gremlin/server/GremlinServerIntegrateTest.java |  16 +++
 gremlin-test/features/sideEffect/Fail.feature      |  46 ++++++++
 .../tinkerpop/gremlin/features/StepDefinition.java |  40 ++++++-
 43 files changed, 895 insertions(+), 129 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f610897..703ba95 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,8 @@ limitations under the License.
 
 * Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
 * Prevented values of `T` to `property()` from being `null`.
+* Added `fail()` step.
+* Improved Gherkin test framework to allow for asserting traversal exceptions as a behavior.
 * Fixed query indentation for profile metrics where indent levels were not being respected.
 * `TraversalOpProcessor` no longer accepts a `String` representation of `Bytecode` for the "gremlin" argument which was left to support older versions of the drivers.
 * Removed requirement that "ids" used to filter vertices and edges need to be all of a single type.
diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index ccddf9c..4114259 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -448,6 +448,7 @@ The "Then" options handle the assertion of the result. There are several options
 * "the result should have a count of _xxx_" - assumes a list value in the result and counts the number of values
 in it
 * "the result should be empty" - no results
+* "the traversal will raise an error" - an exception is thrown as a result of traversal iteration
 * "the result should be ordered" - the exact results and should appear in the order presented
 * "the result should be unordered" - the exact results but can appear any order
 * "the result should be of" - results can be any of the specified values and in any order (use when guarantees
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index c888553..0e7b0ed 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -1228,6 +1228,43 @@ g.V().hasLabel('person').outE().identity().inV().count().is(gt(5)).explain()
 
 For traversal profiling information, please see <<profile-step,`profile()`>>-step.
 
+[[fail-step]]
+=== Fail Step
+
+The `fail()`-step provides a way to force a traversal to immediately fail with an exception. This feature is often
+helpful during debugging purposes and for validating certain conditions prior to continuing with traversal execution.
+
+[source,text]
+----
+gremlin> g.V().has('person','name','peter').fold().
+......1>   coalesce(unfold(),
+......2>            fail('peter should exist')).
+......3>   property('k',100)
+==>v[6]
+gremlin> g.V().has('person','name','stephen').fold().
+......1>   coalesce(unfold(),
+......2>            fail('stephen should exist')).
+......3>   property('k',100)
+fail() Step Triggered
+===========================================================================================================================
+Message > stephen should exist
+Traverser> []
+  Bulk   > 1
+Traversal> fail()
+Parent   > CoalesceStep [V().has("person","name","stephen").fold().coalesce(__.unfold(),__.fail()).property("k",(int) 100)]
+Metadata > {}
+===========================================================================================================================
+----
+
+The code example above exemplifies the latter use case where there is essentially an assertion that there is a vertex
+with a particular "name" value prior to updating the property "k" and explicitly failing when that vertex is not found.
+
+*Additional References*
+
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#fail--++[`fail()`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#fail-java.lang.String-++[`fail(String)`]
+
+
 [[fold-step]]
 === Fold Step
 
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 0f9a7b3..b4ae8d2 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -47,7 +47,28 @@ It is worth noting that `gremlin-groovy` utilized the DSL annotations to constru
 link:https://tinkerpop.apache.org/docs/3.6.0/reference/#credentials-dsl[Credentials DSL] so the `gremlin-annotations`
 package is now explicitly associated to `gremlin-groovy` but as an `<optional>` dependency.
 
-See:link:https://issues.apache.org/jira/browse/TINKERPOP-2411[TINKERPOP-2411]
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2411[TINKERPOP-2411]
+
+==== fail() Step
+
+The new `fail()` step provides a way to immediately terminate a traversal with a runtime exception. In the Gremlin
+Console, the exception will be rendered as follows which helps provide some context to the failure:
+
+[source,text]
+----
+gremlin> g.V().fail("nope!")
+fail() Step Triggered
+=====================
+Message > nope!
+Traverser> v[1]
+  Bulk   > 1
+Traversal> V().fail()
+Metadata > {}
+=====================
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2635[TINKERPOP-2635],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#fail-step[Reference Documentation]
 
 ==== Null for T
 
diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
index c47bbf3..bf5fe0c 100644
--- a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
+++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
@@ -36,6 +36,13 @@ import org.apache.tinkerpop.gremlin.jsr223.CoreGremlinPlugin
 import org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin
 import org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer
 import org.apache.tinkerpop.gremlin.jsr223.console.RemoteException
+import org.apache.tinkerpop.gremlin.process.traversal.Failure
+import org.apache.tinkerpop.gremlin.process.traversal.Step
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep
+import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_LP_NL_O_P_S_SE_SL_Traverser
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation
 import org.apache.tinkerpop.gremlin.structure.Edge
 import org.apache.tinkerpop.gremlin.structure.T
@@ -346,47 +353,54 @@ class Console {
         if (err instanceof Throwable) {
             try {
                 final Throwable e = (Throwable) err
-                String message = e.getMessage()
-                if (null != message) {
-                    message = message.replace("startup failed:", "")
-                    io.err.println(Colorizer.render(Preferences.errorColor, message.trim()))
+
+                // special rendering for Failure
+                if ((err instanceof Failure)) {
+                    def fail = (Failure) err
+                    io.err.println(Colorizer.render(Preferences.errorColor, fail.format()))
                 } else {
-                    io.err.println(Colorizer.render(Preferences.errorColor,e))
-                }
+                    String message = e.getMessage()
+                    if (null != message) {
+                        message = message.replace("startup failed:", "")
+                        io.err.println(Colorizer.render(Preferences.errorColor, message.trim()))
+                    } else {
+                        io.err.println(Colorizer.render(Preferences.errorColor, e))
+                    }
 
-                // provide a hint in the case of a stackoverflow as it can be common when running large Gremlin
-                // scripts and it isn't immediately apparent what the error might mean in this context especially
-                // if the user isn't familiar with the JVM. it really can only be a hint since we can't be completely
-                // sure it arose as a result of a long Gremlin traversal.
-                if (err instanceof StackOverflowError) {
-                    io.err.println(Colorizer.render(Preferences.errorColor,
-                            "A StackOverflowError can indicate that the Gremlin traversal being executed is too long. If " +
-                                    "you have a single Gremlin statement that is \"long\", you may break it up into " +
-                                    "multiple separate commands, re-write the traversal to operate on a stream of " +
-                                    "input via inject() rather than literals, or attempt to increase the -Xss setting" +
-                                    "of the Gremlin Console by modifying gremlin.sh."));
-                }
+                    // provide a hint in the case of a stackoverflow as it can be common when running large Gremlin
+                    // scripts and it isn't immediately apparent what the error might mean in this context especially
+                    // if the user isn't familiar with the JVM. it really can only be a hint since we can't be completely
+                    // sure it arose as a result of a long Gremlin traversal.
+                    if (err instanceof StackOverflowError) {
+                        io.err.println(Colorizer.render(Preferences.errorColor,
+                                "A StackOverflowError can indicate that the Gremlin traversal being executed is too long. If " +
+                                        "you have a single Gremlin statement that is \"long\", you may break it up into " +
+                                        "multiple separate commands, re-write the traversal to operate on a stream of " +
+                                        "input via inject() rather than literals, or attempt to increase the -Xss setting" +
+                                        "of the Gremlin Console by modifying gremlin.sh."));
+                    }
 
-                if (interactive) {
-                    io.err.println(Colorizer.render(Preferences.infoColor,"Type ':help' or ':h' for help."))
-                    io.err.print(Colorizer.render(Preferences.errorColor, "Display stack trace? [yN]"))
-                    io.err.flush()
-                    String line = new BufferedReader(io.in).readLine()
-                    if (null == line)
-                        line = ""
-                    io.err.print(line.trim())
-                    io.err.println()
-                    if (line.trim().equals("y") || line.trim().equals("Y")) {
-                        if (err instanceof RemoteException && err.remoteStackTrace.isPresent()) {
-                            io.err.print(err.remoteStackTrace.get())
-                            io.err.flush()
-                        } else {
-                            e.printStackTrace(io.err)
+                    if (interactive) {
+                        io.err.println(Colorizer.render(Preferences.infoColor, "Type ':help' or ':h' for help."))
+                        io.err.print(Colorizer.render(Preferences.errorColor, "Display stack trace? [yN]"))
+                        io.err.flush()
+                        String line = new BufferedReader(io.in).readLine()
+                        if (null == line)
+                            line = ""
+                        io.err.print(line.trim())
+                        io.err.println()
+                        if (line.trim().equals("y") || line.trim().equals("Y")) {
+                            if (err instanceof RemoteException && err.remoteStackTrace.isPresent()) {
+                                io.err.print(err.remoteStackTrace.get())
+                                io.err.flush()
+                            } else {
+                                e.printStackTrace(io.err)
+                            }
                         }
+                    } else {
+                        e.printStackTrace(io.err)
+                        System.exit(1)
                     }
-                } else {
-                    e.printStackTrace(io.err)
-                    System.exit(1)
                 }
             } catch (Exception ignored) {
                 io.err.println(Colorizer.render(Preferences.errorColor, "An undefined error has occurred: " + err))
@@ -402,6 +416,44 @@ class Console {
         return null
     }
 
+    private def writeTraverserToErrorLines(Traverser t, List errorLines) {
+        // every traverser has an object so toString() that. pad with spaces to cover "side-effects" width
+        errorLines << "Traverser> $t"
+
+        def optGenerator = t.asAdmin().generator
+        if (optGenerator.isPresent()) {
+            def width = "Traverser".length()
+            def generator = optGenerator.get()
+            if (generator.providedRequirements.contains(TraverserRequirement.BULK)) {
+                errorLines << "  Bulk".padRight(width) + "> " + t.bulk()
+            }
+
+            if (generator.providedRequirements.contains(TraverserRequirement.SACK)) {
+                errorLines << "  Sack".padRight(width) + "> " + t.sack()
+            }
+
+            if (generator.providedRequirements.contains(TraverserRequirement.PATH)) {
+                errorLines << "  Path".padRight(width) + "> " + t.path()
+            }
+
+            if (generator.providedRequirements.contains(TraverserRequirement.SINGLE_LOOP) ||
+                    generator.providedRequirements.contains(TraverserRequirement.NESTED_LOOP)) {
+                // flatten loops/names if present
+                def loopNames = t.asAdmin().loopNames
+                def loopsLine = loopNames.isEmpty() ? t.loops() : loopNames.collect { [(it): t.loops(it)]}
+                errorLines << "  Loops".padRight(width) + "> " + loopsLine
+            }
+
+            if (generator.providedRequirements.contains(TraverserRequirement.SIDE_EFFECTS)) {
+                // convert side-effects to a map
+                def sideEffects = t.asAdmin().sideEffects
+                def keys = sideEffects.keys()
+                errorLines << "  S/E".padRight(width) + "> " + keys.collectEntries { [(it): sideEffects.get(it)]}
+            }
+
+        }
+    }
+
     private static String buildResultPrompt() {
         final String groovyshellProperty = System.getProperty("gremlin.prompt")
         if (groovyshellProperty != null)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
index 95f2874..faef075 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
@@ -348,6 +348,19 @@ public class GremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
 	 * {@inheritDoc}
 	 */
 	@Override public T visitTraversalMethod_emit_Traversal(final GremlinParser.TraversalMethod_emit_TraversalContext ctx) { notImplemented(ctx); return null; }
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_fail_Empty(final GremlinParser.TraversalMethod_fail_EmptyContext ctx) { notImplemented(ctx); return null; }
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_fail_String(final GremlinParser.TraversalMethod_fail_StringContext ctx) { notImplemented(ctx); return null; }
+
 	/**
 	 * {@inheritDoc}
 	 */
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index 67258ce..7682232 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -29,6 +29,7 @@ import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 
+import java.util.Map;
 import java.util.function.BiFunction;
 
 import static org.apache.tinkerpop.gremlin.process.traversal.SackFunctions.Barrier.normSack;
@@ -453,6 +454,22 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
      * {@inheritDoc}
      */
     @Override
+    public Traversal visitTraversalMethod_fail_Empty(final GremlinParser.TraversalMethod_fail_EmptyContext ctx) {
+        return this.graphTraversal.fail();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Traversal visitTraversalMethod_fail_String(final GremlinParser.TraversalMethod_fail_StringContext ctx) {
+        return this.graphTraversal.fail(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public GraphTraversal visitTraversalMethod_filter_Predicate(final GremlinParser.TraversalMethod_filter_PredicateContext ctx) {
         return graphTraversal.filter(TraversalPredicateVisitor.getInstance().visitTraversalPredicate(ctx.traversalPredicate()));
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java
new file mode 100644
index 0000000..8e11c87
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java
@@ -0,0 +1,101 @@
+/*
+ * 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.process.traversal;
+
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.FailStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
+import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public interface Failure {
+
+    static Translator.ScriptTranslator TRANSLATOR = GroovyTranslator.of("");
+
+    String getMessage();
+
+    Map<String,Object> getMetadata();
+
+    Traverser.Admin getTraverser();
+
+    Traversal.Admin getTraversal();
+
+    /**
+     * Gets the {@code Failure} as a formatted string representation.
+     */
+    public default String format() {
+        final List<String> lines = new ArrayList<>();
+        final Step parentStep = (Step) getTraversal().getParent();
+
+        lines.add(String.format("Message  > %s", getMessage()));
+        lines.add(String.format("Traverser> %s", getTraverser().toString()));
+
+        final TraverserGenerator generator = getTraversal().getTraverserGenerator();
+        final Traverser.Admin traverser = getTraverser();
+        if (generator.getProvidedRequirements().contains(TraverserRequirement.BULK)) {
+            lines.add(String.format("  Bulk   > %s", traverser.bulk()));
+        }
+        if (generator.getProvidedRequirements().contains(TraverserRequirement.SACK)) {
+            lines.add(String.format("  Sack   > %s", traverser.sack()));
+        }
+        if (generator.getProvidedRequirements().contains(TraverserRequirement.PATH)) {
+            lines.add(String.format("  Path   > %s", traverser.path()));
+        }
+        if (generator.getProvidedRequirements().contains(TraverserRequirement.SINGLE_LOOP) ||
+                generator.getProvidedRequirements().contains(TraverserRequirement.NESTED_LOOP) ) {
+            final Set<String> loopNames = traverser.getLoopNames();
+            final String loopsLine = loopNames.isEmpty() ?
+                    String.valueOf(traverser.asAdmin().loops()) :
+                    loopNames.stream().collect(Collectors.toMap(loopName -> loopName, traverser::loops)).toString();
+            lines.add(String.format("  Loops  > %s", loopsLine));
+        }
+        if (generator.getProvidedRequirements().contains(TraverserRequirement.SIDE_EFFECTS)) {
+            final TraversalSideEffects tse = traverser.getSideEffects();
+            final Set<String> keys = tse.keys();
+            lines.add(String.format("  S/E    > %s", keys.stream().collect(Collectors.toMap(k -> k, tse::get))));
+        }
+
+        // removes the starting period so that "__.out()" simply presents as "out()"
+        lines.add(String.format("Traversal> %s", TRANSLATOR.translate(getTraversal()).getScript().substring(1)));
+
+        // not sure there is a situation where fail() would be used where it was not wrapped in a parent,
+        // but on the odd case that it is it can be handled
+        if (parentStep != EmptyStep.instance()) {
+            lines.add(String.format("Parent   > %s [%s]",
+                    parentStep.getClass().getSimpleName(), TRANSLATOR.translate(parentStep.getTraversal()).getScript().substring(1)));
+        }
+
+        lines.add(String.format("Metadata > %s", getMetadata()));
+
+        final int longestLineLength = lines.stream().mapToInt(String::length).max().getAsInt();
+        final String separatorLine = String.join("", Collections.nCopies(longestLineLength, "="));
+        lines.add(0, separatorLine);
+        lines.add(0, "fail() Step Triggered");
+        lines.add(separatorLine);
+
+        return String.join(System.lineSeparator(), lines);
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
index fc58c62..19eb689 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
@@ -22,7 +22,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep;
 import org.apache.tinkerpop.gremlin.structure.util.Attachable;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -325,5 +327,12 @@ public interface Traverser<T> extends Serializable, Comparable<Traverser<T>>, Cl
          * @return the set of tags associated with the traverser.
          */
         public Set<String> getTags();
+
+        /**
+         * If this traverser supports loops then return the loop names if any.
+         */
+        public default Set<String> getLoopNames() {
+            return Collections.emptySet();
+        }
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index 8d11664..891ead0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PageRank
 import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PeerPressureVertexProgramStep;
 import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ProgramVertexProgramStep;
 import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ShortestPathVertexProgramStep;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
@@ -126,6 +127,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateGlobalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.FailStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupCountSideEffectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupSideEffectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
@@ -1254,6 +1256,18 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     }
 
     /**
+     * Filter all traversers in the traversal. This step has narrow use cases and is primarily intended for use as a
+     * signal to remote servers that {@link #iterate()} was called. While it may be directly used, it is often a sign
+     * that a traversal should be re-written in another form.
+     *
+     * @return the updated traversal with respective {@link NoneStep}.
+     */
+    @Override
+    default GraphTraversal<S, E> none() {
+        return (GraphTraversal<S, E>) Traversal.super.none();
+    }
+
+    /**
      * Ensures that at least one of the provided traversals yield a result.
      *
      * @param orTraversals filter traversals where at least one must be satisfied
@@ -2154,6 +2168,33 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     }
 
     /**
+     * When triggered, immediately throws a {@code RuntimeException} which implements the {@link Failure} interface.
+     * The traversal will be terminated as a result.
+     *
+     * @return the traversal with an appended {@link FailStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#fail-step" target="_blank">Reference Documentation - Fail Step</a>
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, E> fail() {
+        this.asAdmin().getBytecode().addStep(Symbols.fail);
+        return this.asAdmin().addStep(new FailStep<>(this.asAdmin()));
+    }
+
+    /**
+     * When triggered, immediately throws a {@code RuntimeException} which implements the {@link Failure} interface.
+     * The traversal will be terminated as a result.
+     *
+     * @param message the error message to include in the exception
+     * @return the traversal with an appended {@link FailStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#fail-step" target="_blank">Reference Documentation - Fail Step</a>
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, E> fail(final String message) {
+        this.asAdmin().getBytecode().addStep(Symbols.fail, message);
+        return this.asAdmin().addStep(new FailStep<>(this.asAdmin(), message));
+    }
+
+    /**
      * Aggregates the emanating paths into a {@link Tree} data structure.
      *
      * @param sideEffectKey the name of the side-effect key that will hold the tree
@@ -2222,18 +2263,6 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     }
 
     /**
-     * Filter all traversers in the traversal. This step has narrow use cases and is primarily intended for use as a
-     * signal to remote servers that {@link #iterate()} was called. While it may be directly used, it is often a sign
-     * that a traversal should be re-written in another form.
-     *
-     * @return the updated traversal with respective {@link NoneStep}.
-     */
-    @Override
-    default GraphTraversal<S, E> none() {
-        return (GraphTraversal<S, E>) Traversal.super.none();
-    }
-
-    /**
      * Sets a {@link Property} value and related meta properties if supplied, if supported by the {@link Graph}
      * and if the {@link Element} is a {@link VertexProperty}.  This method is the long-hand version of
      * {@link #property(Object, Object, Object...)} with the difference that the {@link VertexProperty.Cardinality}
@@ -3156,6 +3185,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         @Deprecated
         public static final String store = "store";
         public static final String aggregate = "aggregate";
+        public static final String fail = "fail";
         public static final String subgraph = "subgraph";
         public static final String barrier = "barrier";
         public static final String index = "index";
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index aab83f4..08a4c07 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -912,6 +912,20 @@ public class __ {
     }
 
     /**
+     * @see GraphTraversal#fail()
+     */
+    public static <A>  GraphTraversal<A, A> fail() {
+        return __.<A>start().fail();
+    }
+
+    /**
+     * @see GraphTraversal#fail(String)
+     */
+    public static <A>  GraphTraversal<A, A> fail(final String message) {
+        return __.<A>start().fail(message);
+    }
+
+    /**
      * @see GraphTraversal#group(String)
      */
     public static <A> GraphTraversal<A, A> group(final String sideEffectKey) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
new file mode 100644
index 0000000..48741f8
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
@@ -0,0 +1,87 @@
+/*
+ * 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.process.traversal.step.sideEffect;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Triggers an immediate failure of the traversal by throwing a {@code RuntimeException}. The exception thrown must
+ * implement the {@link Failure} interface.
+ */
+public class FailStep<S> extends SideEffectStep<S> {
+
+    protected String message;
+    protected Map<String,Object> metadata;
+
+    public FailStep(final Traversal.Admin traversal) {
+        this(traversal, "fail() step triggered");
+    }
+
+    public FailStep(final Traversal.Admin traversal, final String message) {
+        this(traversal, message, Collections.emptyMap());
+    }
+
+    public FailStep(final Traversal.Admin traversal, final String message, final Map<String,Object> metadata) {
+        super(traversal);
+        this.message = message;
+        this.metadata = metadata;
+    }
+
+    @Override
+    protected void sideEffect(final Traverser.Admin<S> traverser) {
+        throw new FailException(traversal, traverser, message, metadata);
+    }
+
+    /**
+     * Default {@link Failure} implementation that is thrown by {@link FailStep}.
+     */
+    public static class FailException extends RuntimeException implements Failure {
+        private final Map<String,Object> metadata;
+        private final Traversal.Admin traversal;
+        private final Traverser.Admin traverser;
+
+        public FailException(final Traversal.Admin traversal, final Traverser.Admin traverser,
+                             final String message, final Map<String,Object> metadata) {
+            super(message);
+            this.metadata = metadata;
+            this.traversal = traversal;
+            this.traverser = traverser;
+        }
+
+        @Override
+        public Map<String, Object> getMetadata() {
+            return metadata;
+        }
+
+        @Override
+        public Traverser.Admin getTraverser() {
+            return traverser;
+        }
+
+        @Override
+        public Traversal.Admin getTraversal() {
+            return traversal;
+        }
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
index 59287ac..401bfd2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
 
 import java.util.Iterator;
+import java.util.Set;
 import java.util.Stack;
 
 public class B_LP_NL_O_S_SE_SL_Traverser<T> extends B_LP_O_S_SE_SL_Traverser<T> {
@@ -65,6 +66,10 @@ public class B_LP_NL_O_S_SE_SL_Traverser<T> extends B_LP_O_S_SE_SL_Traverser<T>
                 this.loopNames.put(loopName, lc);
         }
     }
+    @Override
+    public Set<String> getLoopNames() {
+        return loopNames.keySet();
+    }
 
     @Override
     public void incrLoops() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
index 3a2aa38..1687909 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
@@ -19,7 +19,6 @@
 package org.apache.tinkerpop.gremlin.process.traversal.traverser;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
-import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.ImmutablePath;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
index 20ed1be..6e18d12 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
@@ -18,7 +18,6 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.traverser;
 
-import org.apache.commons.collections.MapIterator;
 import org.apache.commons.collections.map.ReferenceMap;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
index 57fa47a..904d8c7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
@@ -22,7 +22,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -72,6 +74,11 @@ public class B_O_S_SE_SL_Traverser<T> extends B_O_Traverser<T> {
     }
 
     @Override
+    public Set<String> getLoopNames() {
+        return Collections.singleton(loopName);
+    }
+
+    @Override
     public void initialiseLoops(final String stepLabel , final String loopName){
         this.loopName = loopName;
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
index 3cb1dd3..77157b8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
 
 import java.util.Iterator;
+import java.util.Set;
 import java.util.Stack;
 
 public class LP_NL_O_OB_P_S_SE_SL_Traverser<T> extends LP_O_OB_P_S_SE_SL_Traverser<T> {
@@ -130,6 +131,11 @@ public class LP_NL_O_OB_P_S_SE_SL_Traverser<T> extends LP_O_OB_P_S_SE_SL_Travers
         super.merge(other);
     }
 
+    @Override
+    public Set<String> getLoopNames() {
+        return loopNames.keySet();
+    }
+
     /////////////////
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
index a9cd7fb..14c2fe3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
 
 import java.util.Iterator;
+import java.util.Set;
 import java.util.Stack;
 
 public class LP_NL_O_OB_S_SE_SL_Traverser<T> extends LP_O_OB_S_SE_SL_Traverser<T> {
@@ -58,6 +59,11 @@ public class LP_NL_O_OB_S_SE_SL_Traverser<T> extends LP_O_OB_S_SE_SL_Traverser<T
     }
 
     @Override
+    public Set<String> getLoopNames() {
+        return loopNames.keySet();
+    }
+
+    @Override
     public void initialiseLoops(final String stepLabel, final String loopName) {
         if (this.nestedLoops.empty() || !this.nestedLoops.peek().hasLabel(stepLabel)) {
             final LabelledCounter lc = new LabelledCounter(stepLabel, (short) 0);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
index f6bb9cc..97038fe 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
@@ -20,7 +20,6 @@
 package org.apache.tinkerpop.gremlin.process.traversal.traverser;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
-import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.ImmutablePath;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
index 72f43fe..c30e893 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
 
 import java.util.Iterator;
+import java.util.Set;
 import java.util.Stack;
 
 public class NL_O_OB_S_SE_SL_Traverser<T> extends O_OB_S_SE_SL_Traverser<T> {
@@ -130,6 +131,11 @@ public class NL_O_OB_S_SE_SL_Traverser<T> extends O_OB_S_SE_SL_Traverser<T> {
         super.merge(other);
     }
 
+    @Override
+    public Set<String> getLoopNames() {
+        return loopNames.keySet();
+    }
+
     /////////////////
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
index 601bcda..73dc644 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
@@ -23,7 +23,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -78,13 +80,18 @@ public class O_OB_S_SE_SL_Traverser<T> extends O_Traverser<T> {
 
     @Override
     public int loops(final String loopName) {
-        if (loopName == null || this.loopName != null && this.loopName.equals(loopName))
+        if (loopName == null || this.loopName.equals(loopName))
             return this.loops;
         else
             throw new IllegalArgumentException("Loop name not defined: " + loopName);
     }
 
     @Override
+    public Set<String> getLoopNames() {
+        return Collections.singleton(loopName);
+    }
+
+    @Override
     public void initialiseLoops(final String stepLabel , final String loopName){
         this.loopName = loopName;
     }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index 328e506..ebd1cdf 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -543,6 +543,33 @@ namespace Gremlin.Net.Process.Traversal
         }
 
         /// <summary>
+        ///     Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, E> Fail ()
+        {
+            Bytecode.AddStep("fail");
+            return Wrap<S, E>(this);
+        }
+
+        /// <summary>
+        ///     Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, E> Fail (string msg)
+        {
+            Bytecode.AddStep("fail", msg);
+            return Wrap<S, E>(this);
+        }
+
+        /// <summary>
+        ///     Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, E> Fail (string msg, IDictionary<string,object> m)
+        {
+            Bytecode.AddStep("fail", msg, m);
+            return Wrap<S, E>(this);
+        }
+
+        /// <summary>
         ///     Adds the filter step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
         public GraphTraversal<S, E> Filter (IPredicate predicate)
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index fa1c027..acad588 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -364,6 +364,22 @@ namespace Gremlin.Net.Process.Traversal
         }
 
         /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the fail step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, object> Fail()
+        {
+            return new GraphTraversal<object, object>().Fail();
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the fail step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, object> Fail(string msg)
+        {
+            return new GraphTraversal<object, object>().Fail(msg);
+        }
+
+        /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the filter step to that traversal.
         /// </summary>
         public static GraphTraversal<object, object> Filter(IPredicate predicate)
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 11e71c8..be8973c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -47,6 +47,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
         private readonly IDictionary<string, object> _parameters = new Dictionary<string, object>();
         private ITraversal _traversal;
         private object[] _result;
+        private Exception _error = null;
         private static readonly JsonSerializerOptions JsonDeserializingOptions = new JsonSerializerOptions
             {PropertyNamingPolicy = JsonNamingPolicy.CamelCase};
         
@@ -157,11 +158,18 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             }
             ITraversal t = _traversal;
             var list = new List<object>();
-            while (t.MoveNext())
+            try
             {
-                list.Add(t.Current);
+                while (t.MoveNext())
+                {
+                    list.Add(t.Current);
+                }
+                _result = list.ToArray();
+            }
+            catch (Exception ex)
+            {
+                _error = ex;
             }
-            _result = list.ToArray();
         }
 
         [When("iterated next")]
@@ -171,26 +179,44 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             {
                 throw new InvalidOperationException("Traversal should be set before iterating");
             }
-            _traversal.MoveNext();
-            var result = _traversal.Current;
-            switch (result)
+
+            try
             {
-                case null:
-                    _result = null;
-                    return;
-                case object[] arrayResult:
-                    _result = arrayResult;
-                    return;
-                case IEnumerable enumerableResult:
-                    _result = enumerableResult.Cast<object>().ToArray();
-                    return;
+                _traversal.MoveNext();
+                var result = _traversal.Current;
+                switch (result)
+                {
+                    case null:
+                        _result = null;
+                        return;
+                    case object[] arrayResult:
+                        _result = arrayResult;
+                        return;
+                    case IEnumerable enumerableResult:
+                        _result = enumerableResult.Cast<object>().ToArray();
+                        return;
+                }
+            }
+            catch (Exception ex)
+            {
+                _error = ex;
             }
-            throw new InvalidCastException($"Can not convert instance of {result.GetType()} to object[]");
+        }
+
+        [Then("the traversal will raise an error")]
+        public void TraversalWillRaiseError()
+        {
+            Assert.NotNull(_error);
+
+            // consume the error now that it has been asserted
+            _error = null;
         }
 
         [Then("the result should be (\\w+)")]
         public void AssertResult(string characterizedAs, DataTable table = null)
         {
+            assertThatNoErrorWasThrown();
+
             var ordered = characterizedAs == "ordered";
             switch (characterizedAs)
             {
@@ -230,12 +256,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
         [Then("the result should have a count of (\\d+)")]
         public void AssertCount(int count)
         {
+            assertThatNoErrorWasThrown();
+
             Assert.Equal(count, _result.Length);
         }
 
         [Then("the graph should return (\\d+) for count of (.+)")]
         public void AssertTraversalCount(int expectedCount, string traversalText)
         {
+            assertThatNoErrorWasThrown();
+
             if (traversalText.StartsWith("\""))
             {
                 traversalText = traversalText.Substring(1, traversalText.Length - 2);
@@ -258,6 +288,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             
         }
 
+        private void assertThatNoErrorWasThrown()
+        {
+            if (_error != null) throw _error;
+        }
+
         private static object ToMap(string stringMap, string graphName)
         {
             var jsonMap = JsonSerializer.Deserialize<JsonElement>(stringMap, JsonDeserializingOptions);
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 40bae6a..8977404 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -456,7 +456,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_name_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Max<object>()}}, 
                {"g_V_age_fold_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Max<object>(Scope.Local)}}, 
                {"g_V_aggregateXaX_byXageX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("age").Cap<object>("a").Max<object>(Scope.Local)}}, 
+               {"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("age").Cap<object>("a").Max<object>(Scope.Local)}}, 
                {"g_V_aggregateXaX_byXageX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("age").Cap<object>("a").Unfold<object>().Max<object>()}}, 
+               {"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("age").Cap<object>("a").Unfold<object>().Max<object>()}}, 
                {"g_V_aggregateXaX_byXfooX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("foo").Cap<object>("a").Max<object>(Scope.Local)}}, 
                {"g_V_aggregateXaX_byXfooX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("foo").Cap<object>("a").Unfold<object>().Max<object>()}}, 
                {"g_V_foo_fold_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Fold().Max<object>(Scope.Local)}}, 
@@ -715,6 +717,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate(Scope.Local,"a").By(__.OutE("created").Count()).Out().Out().Aggregate(Scope.Local,"a").By(__.InE("created").Values<object>("weight").Sum<object>()).Cap<object>("a")}}, 
                {"g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("x").By(__.Values<object>("age").Is(P.Gt(29))).Cap<object>("x")}}, 
                {"g_V_aggregateXxX_byXout_order_byXnameXX_capXxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("x").By(__.Out().Order().By("name")).Cap<object>("x")}}, 
+               {"g_V_fail", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail()}}, 
+               {"g_V_failXmsgX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail("msg")}}, 
+               {"g_V_unionXout_failX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Union<object>(__.Out(),__.Fail())}}, 
                {"g_V_group_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("name")}}, 
                {"g_V_group_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("age")}}, 
                {"g_V_group_byXnameX_by", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("name").By()}}, 
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
index c5936a2..ba9b6fd 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.driver;
 
 import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 
@@ -96,8 +97,22 @@ public final class Tokens {
 
     public static final String VAL_TRAVERSAL_SOURCE_ALIAS = "g";
 
+    /**
+     * The value of this key holds a string representation of the data held by a {@link Failure} as produced by
+     * {@link Failure#format()}.
+     */
+    public static final String STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE = "failStepMessage";
+
+    /**
+     * Refers to the hierarchy of exception names for a particular exception thrown on the server.
+     */
     public static final String STATUS_ATTRIBUTE_EXCEPTIONS = "exceptions";
+
+    /**
+     * Refers to the stacktrace for an exception thrown on the server
+     */
     public static final String STATUS_ATTRIBUTE_STACK_TRACE = "stackTrace";
+
     /**
      * A {@link ResultSet#statusAttributes()} key for user-facing warnings.
      * <p>
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
index 4f43f65..88fce82 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
@@ -18,6 +18,9 @@
  */
 package org.apache.tinkerpop.gremlin.driver.message;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -114,6 +117,14 @@ public enum ResponseStatusCode {
     SERVER_ERROR(500),
 
     /**
+     * A server error that is produced when the {@link GraphTraversal#fail()} step is triggered. The returned exception
+     * will include information consistent with the {@link Failure} interface.
+     *
+     * @since 3.6.0
+     */
+    SERVER_ERROR_FAIL_STEP(595),
+
+    /**
      * A server error that indicates that the client should retry the request. A graph will typically return this error
      * when a transaction fails due to a locking exception or some other sort of concurrent modification. In other
      * words, the request was likely valid but the state of the server at the particular time the request arrived
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 5cee02b..1da973d 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
@@ -115,6 +115,9 @@ class VarAsBindingASTTransformation implements ASTTransformation {
                                 case GraphTraversal.Symbols.by:
                                     if (i == 1) bindingValue = new PropertyExpression(new ClassExpression(new ClassNode(Order)), "desc")
                                     break
+                                case GraphTraversal.Symbols.fail:
+                                    if (i == 1) bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
+                                    break
                             }
                             def bindingExpression = createBindingFromVar(entry.text, bindingVariableName, bindingValue)
                             bindingExpression.sourcePosition = entry
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index e45e0e2..6c49ec5 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -500,6 +500,16 @@ class GraphTraversal extends Traversal {
     this.bytecode.addStep('emit', args);
     return this;
   }
+
+  /**
+   * Graph traversal fa method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  fail(...args) {
+    this.bytecode.addStep('fail', args);
+    return this;
+  }
   
   /**
    * Graph traversal filter method.
@@ -1325,6 +1335,7 @@ const statics = {
   drop: (...args) => callOnEmptyTraversal('drop', args),
   elementMap: (...args) => callOnEmptyTraversal('elementMap', args),
   emit: (...args) => callOnEmptyTraversal('emit', args),
+  fail: (...args) => callOnEmptyTraversal('fail', args),
   filter: (...args) => callOnEmptyTraversal('filter', args),
   flatMap: (...args) => callOnEmptyTraversal('flatMap', args),
   fold: (...args) => callOnEmptyTraversal('fold', args),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index dd5c562..5205f85 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -128,7 +128,7 @@ Given(/^using the parameter (.+) defined as "(.+)"$/, function (paramName, strin
 });
 
 When('iterated to list', function () {
-  return this.traversal.toList().then(list => this.result = list);
+  return this.traversal.toList().then(list => this.result = list).catch(err => this.result = err);
 });
 
 When('iterated next', function () {
@@ -138,10 +138,16 @@ When('iterated next', function () {
       // Compare using the objects array
       this.result = this.result.objects;
     }
-  });
+  }).catch(err => this.result = err);
+});
+
+Then('the traversal will raise an error', function() {
+  expect(this.result).to.be.a.instanceof(Error);
 });
 
 Then(/^the result should be (\w+)$/, function assertResult(characterizedAs, resultTable) {
+  expect(this.result).to.not.be.a.instanceof(Error);
+
   if (characterizedAs === 'empty') {
     expect(this.result).to.be.empty;
     if (typeof resultTable === 'function'){
@@ -165,6 +171,8 @@ Then(/^the result should be (\w+)$/, function assertResult(characterizedAs, resu
 });
 
 Then(/^the graph should return (\d+) for count of "(.+)"$/, function (stringCount, traversalText) {
+  expect(this.result).to.not.be.a.instanceof(Error);
+
   const p = Object.assign({}, this.parameters);
   p.g = this.g;
   const traversal = gremlin[this.scenario].shift()(p);
@@ -174,6 +182,8 @@ Then(/^the graph should return (\d+) for count of "(.+)"$/, function (stringCoun
 });
 
 Then(/^the result should have a count of (\d+)$/, function (stringCount) {
+  expect(this.result).to.not.be.a.instanceof(Error);
+
   const expected = parseInt(stringCount, 10);
   if (!Array.isArray(this.result)) {
     let count = 0;
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 6f49924..d303f97 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -703,6 +703,9 @@ const gremlins = {
     g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX: [function({g}) { return g.V().aggregate(Scope.local,"a").by(__.outE("created").count()).out().out().aggregate(Scope.local,"a").by(__.inE("created").values("weight").sum()).cap("a") }], 
     g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX: [function({g}) { return g.V().aggregate("x").by(__.values("age").is(P.gt(29))).cap("x") }], 
     g_V_aggregateXxX_byXout_order_byXnameXX_capXxX: [function({g}) { return g.V().aggregate("x").by(__.out().order().by("name")).cap("x") }], 
+    g_V_fail: [function({g}) { return g.V().fail() }], 
+    g_V_failXmsgX: [function({g}) { return g.V().fail("msg") }], 
+    g_V_unionXout_failX: [function({g}) { return g.V().union(__.out(),__.fail()) }], 
     g_V_group_byXnameX: [function({g}) { return g.V().group().by("name") }], 
     g_V_group_byXageX: [function({g}) { return g.V().group().by("age") }], 
     g_V_group_byXnameX_by: [function({g}) { return g.V().group().by("name").by() }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index a1ebb53..4cbc8a4 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -240,6 +240,7 @@ traversalMethod
 	| traversalMethod_subgraph
 	| traversalMethod_sum
 	| traversalMethod_tail
+	| traversalMethod_fail
 	| traversalMethod_timeLimit
 	| traversalMethod_times
 	| traversalMethod_to
@@ -668,6 +669,11 @@ traversalMethod_tail
 	| 'tail' LPAREN integerLiteral RPAREN #traversalMethod_tail_long
 	;
 
+traversalMethod_fail
+	: 'fail' LPAREN RPAREN #traversalMethod_fail_Empty
+	| 'fail' LPAREN stringLiteral RPAREN #traversalMethod_fail_String
+	;
+
 traversalMethod_timeLimit
 	: 'timeLimit' LPAREN integerLiteral RPAREN
 	;
diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
index b56988b..770821e 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
@@ -253,6 +253,10 @@ class GraphTraversal(Traversal):
         self.bytecode.add_step("emit", *args)
         return self
 
+    def fail(self, *args):
+        self.bytecode.add_step("fail", *args)
+        return self
+
     def filter_(self, *args):
         self.bytecode.add_step("filter", *args)
         return self
@@ -679,6 +683,10 @@ class __(object, metaclass=MagicType):
         return cls.graph_traversal(None, None, Bytecode()).emit(*args)
 
     @classmethod
+    def fail(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).fail(*args)
+
+    @classmethod
     def filter_(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).filter_(*args)
 
@@ -1039,6 +1047,10 @@ def emit(*args):
     return __.emit(*args)
 
 
+def fail(*args):
+    return __.fail(*args)
+
+
 def filter_(*args):
     return __.filter_(*args)
 
@@ -1355,6 +1367,8 @@ statics.add_static('elementMap', elementMap)
 
 statics.add_static('emit', emit)
 
+statics.add_static('fail', fail)
+
 statics.add_static('filter_', filter_)
 
 statics.add_static('flatMap', flatMap)
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py b/gremlin-python/src/main/python/radish/feature_steps.py
index 9fd8cf6..a1f76ba 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -95,8 +95,12 @@ def translate_traversal(step):
 def iterate_the_traversal(step):
     if step.context.ignore:
         return
-    
-    step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.toList()))
+
+    try:
+        step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.toList()))
+        step.context.failed = False
+    except:
+        step.context.failed = True
 
 
 @when("iterated next")
@@ -104,14 +108,24 @@ def next_the_traversal(step):
     if step.context.ignore:
         return
 
-    step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.next()))
+    try:
+        step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.next()))
+        step.context.failed = False
+    except:
+        step.context.failed = True
+
 
+@then("the traversal will raise an error")
+def raise_an_error(step):
+    assert_that(step.context.failed, equal_to(True))
 
 @then("the result should be {characterized_as:w}")
 def assert_result(step, characterized_as):
     if step.context.ignore:
         return
 
+    assert_that(step.context.failed, equal_to(False))
+
     if characterized_as == "empty":        # no results
         assert_that(len(step.context.result), equal_to(0))
     elif characterized_as == "ordered":    # results asserted in the order of the data table
@@ -129,6 +143,8 @@ def assert_side_effects(step, count, traversal_string):
     if step.context.ignore:
         return
 
+    assert_that(step.context.failed, equal_to(False))
+
     p = step.context.traversal_params if hasattr(step.context, "traversal_params") else {}
     p['g'] = step.context.g
     t = step.context.traversals.pop(0)(**p)
@@ -141,6 +157,8 @@ def assert_count(step, count):
     if step.context.ignore:
         return
 
+    assert_that(step.context.failed, equal_to(False))
+
     assert_that(len(list(step.context.result)), equal_to(count))
 
 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 652d491..13900d6 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -688,6 +688,9 @@ world.gremlins = {
     'g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX': [(lambda g:g.V().aggregate(Scope.local,'a').by(__.outE('created').count()).out().out().aggregate(Scope.local,'a').by(__.inE('created').weight.sum_()).cap('a'))], 
     'g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX': [(lambda g:g.V().aggregate('x').by(__.age.is_(P.gt(29))).cap('x'))], 
     'g_V_aggregateXxX_byXout_order_byXnameXX_capXxX': [(lambda g:g.V().aggregate('x').by(__.out().order().by('name')).cap('x'))], 
+    'g_V_fail': [(lambda g:g.V().fail())], 
+    'g_V_failXmsgX': [(lambda g:g.V().fail('msg'))], 
+    'g_V_unionXout_failX': [(lambda g:g.V().union(__.out(),__.fail()))], 
     'g_V_group_byXnameX': [(lambda g:g.V().group().by('name'))], 
     'g_V_group_byXageX': [(lambda g:g.V().group().by('age'))], 
     'g_V_group_byXnameX_by': [(lambda g:g.V().group().by('name').by())], 
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
index 65c26be..ed63dc6 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
@@ -35,6 +35,7 @@ import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException
 import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine;
 import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.GraphOp;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
@@ -278,14 +279,20 @@ public abstract class AbstractSession implements Session, AutoCloseable {
     protected void handleException(final SessionTask sessionTask, final Throwable t) throws SessionException {
         if (t instanceof SessionException) throw (SessionException) t;
 
-        final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(t);
-        if (possibleTemporaryException.isPresent()) {
-            final Throwable temporaryException = possibleTemporaryException.get();
-            throw new SessionException(temporaryException.getMessage(), t,
-                    ResponseMessage.build(sessionTask.getRequestMessage())
-                            .code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                            .statusMessage(temporaryException.getMessage())
-                            .statusAttributeException(temporaryException).create());
+        final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
+        if (possibleSpecialException.isPresent()) {
+            final Throwable special = possibleSpecialException.get();
+            final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(sessionTask.getRequestMessage()).
+                    statusMessage(special.getMessage()).
+                    statusAttributeException(special);
+            if (special instanceof TemporaryException) {
+                specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+            } else if (special instanceof Failure) {
+                final Failure failure = (Failure) special;
+                specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                        statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+            }
+            throw new SessionException(special.getMessage(), specialResponseMsg.create());
         }
 
         final Throwable root = ExceptionUtils.getRootCause(t);
@@ -373,12 +380,12 @@ public abstract class AbstractSession implements Session, AutoCloseable {
     }
 
     /**
-     * Check if any exception in the chain is TemporaryException then we should respond with the right error code so
-     * that the client knows to retry.
+     * Check if any exception in the chain is {@link TemporaryException} or {@link Failure} then respond with the
+     * right error code so that the client knows to retry.
      */
-    protected Optional<Throwable> determineIfTemporaryException(final Throwable ex) {
+    protected static Optional<Throwable> determineIfSpecialException(final Throwable ex) {
         return Stream.of(ExceptionUtils.getThrowables(ex)).
-                filter(i -> i instanceof TemporaryException).findFirst();
+                filter(i -> i instanceof TemporaryException || i instanceof Failure).findFirst();
     }
 
     /**
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
index 2a8ecbe..cbd14a1 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
 import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
 import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -38,6 +39,7 @@ import org.apache.tinkerpop.gremlin.server.Context;
 import org.apache.tinkerpop.gremlin.server.GremlinServer;
 import org.apache.tinkerpop.gremlin.server.Settings;
 import org.apache.tinkerpop.gremlin.server.util.MetricManager;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
 import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import org.codehaus.groovy.control.MultipleCompilationErrorsException;
@@ -272,13 +274,22 @@ public abstract class AbstractEvalOpProcessor extends AbstractOpProcessor {
                 timerContext.stop();
 
                 if (t != null) {
-                    // if any exception in the chain is TemporaryException then we should respond with the right error
-                    // code so that the client knows to retry
-                    final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(t);
-                    if (possibleTemporaryException.isPresent()) {
-                        ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                                .statusMessage(possibleTemporaryException.get().getMessage())
-                                .statusAttributeException(possibleTemporaryException.get()).create());
+                    // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                    // right error code so that the client knows to retry
+                    final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
+                    if (possibleSpecialException.isPresent()) {
+                        final Throwable special = possibleSpecialException.get();
+                        final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                                statusMessage(special.getMessage()).
+                                statusAttributeException(special);
+                        if (special instanceof TemporaryException) {
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                        } else if (special instanceof Failure) {
+                            final Failure failure = (Failure) special;
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                    statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                        }
+                        ctx.writeAndFlush(specialResponseMsg.create());
                     } else if (t instanceof OpProcessorException) {
                         ctx.writeAndFlush(((OpProcessorException) t).getResponseMessage());
                     } else if (t instanceof TimedInterruptTimeoutException) {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
index 1498c51..c11af3b 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
 import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
 import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
 import org.apache.tinkerpop.gremlin.driver.ser.MessageTextSerializer;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.server.Context;
 import org.apache.tinkerpop.gremlin.server.GraphManager;
 import org.apache.tinkerpop.gremlin.server.OpProcessor;
@@ -67,12 +68,12 @@ public abstract class AbstractOpProcessor implements OpProcessor {
     }
 
     /**
-     * Check if any exception in the chain is TemporaryException then we should respond with the right error code so
-     * that the client knows to retry.
+     * Check if any exception in the chain is {@link TemporaryException} or {@link Failure} then respond with the
+     * right error code so that the client knows to retry.
      */
-    protected static Optional<Throwable> determineIfTemporaryException(final Throwable ex) {
+    protected static Optional<Throwable> determineIfSpecialException(final Throwable ex) {
         return Stream.of(ExceptionUtils.getThrowables(ex)).
-                filter(i -> i instanceof TemporaryException).findFirst();
+                filter(i -> i instanceof TemporaryException || i instanceof Failure).findFirst();
     }
 
     /**
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
index 6a18645..a5df693 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin;
 import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.util.BytecodeHelper;
@@ -46,6 +47,7 @@ import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper;
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
 import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
@@ -419,13 +421,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
                     if (ex instanceof UndeclaredThrowableException)
                         t = t.getCause();
 
-                    // if any exception in the chain is TemporaryException then we should respond with the right error
-                    // code so that the client knows to retry
-                    final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
-                    if (possibleTemporaryException.isPresent()) {
-                        context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                                .statusMessage(possibleTemporaryException.get().getMessage())
-                                .statusAttributeException(possibleTemporaryException.get()).create());
+                    // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                    // right error code so that the client knows to retry
+                    final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+                    if (possibleSpecialException.isPresent()) {
+                        final Throwable special = possibleSpecialException.get();
+                        final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                                statusMessage(special.getMessage()).
+                                statusAttributeException(special);
+                        if (special instanceof TemporaryException) {
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                        } else if (special instanceof Failure) {
+                            final Failure failure = (Failure) special;
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                    statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                        }
+                        context.writeAndFlush(specialResponseMsg.create());
                     } else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
                         final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
                         logger.warn(errorMessage);
@@ -441,11 +452,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
                     onError(graph, context);
                 }
             } catch (Exception ex) {
-                final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
-                if (possibleTemporaryException.isPresent()) {
-                    context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                            .statusMessage(possibleTemporaryException.get().getMessage())
-                            .statusAttributeException(possibleTemporaryException.get()).create());
+                // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                // right error code so that the client knows to retry
+                final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+                if (possibleSpecialException.isPresent()) {
+                    final Throwable special = possibleSpecialException.get();
+                    final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                            statusMessage(special.getMessage()).
+                            statusAttributeException(special);
+                    if (special instanceof TemporaryException) {
+                        specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                    } else if (special instanceof Failure) {
+                        final Failure failure = (Failure) special;
+                        specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                    }
+                    context.writeAndFlush(specialResponseMsg.create());
                 } else {
                     logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
                     context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
@@ -501,11 +523,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
                                 .create());
 
                     } catch (Exception ex) {
-                        final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
-                        if (possibleTemporaryException.isPresent()) {
-                            context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                                    .statusMessage(possibleTemporaryException.get().getMessage())
-                                    .statusAttributeException(possibleTemporaryException.get()).create());
+                        // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                        // right error code so that the client knows to retry
+                        final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+                        if (possibleSpecialException.isPresent()) {
+                            final Throwable special = possibleSpecialException.get();
+                            final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                                    statusMessage(special.getMessage()).
+                                    statusAttributeException(special);
+                            if (special instanceof TemporaryException) {
+                                specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                            } else if (special instanceof Failure) {
+                                final Failure failure = (Failure) special;
+                                specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                        statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                            }
+                            context.writeAndFlush(specialResponseMsg.create());
                         } else {
                             logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
                             context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
index 80be01f..620332f 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
 import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
 import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.util.BytecodeHelper;
@@ -38,13 +39,13 @@ import org.apache.tinkerpop.gremlin.server.OpProcessor;
 import org.apache.tinkerpop.gremlin.server.Settings;
 import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
 import org.apache.tinkerpop.gremlin.server.handler.Frame;
-import org.apache.tinkerpop.gremlin.server.handler.SessionException;
 import org.apache.tinkerpop.gremlin.server.handler.StateKey;
 import org.apache.tinkerpop.gremlin.server.op.AbstractOpProcessor;
 import org.apache.tinkerpop.gremlin.server.op.OpProcessorException;
 import org.apache.tinkerpop.gremlin.server.util.MetricManager;
 import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
 import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
 import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -222,13 +223,22 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
                     if (ex instanceof UndeclaredThrowableException)
                         t = t.getCause();
 
-                    // if any exception in the chain is TemporaryException then we should respond with the right error
-                    // code so that the client knows to retry
-                    final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
-                    if (possibleTemporaryException.isPresent()) {
-                        context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                                .statusMessage(possibleTemporaryException.get().getMessage())
-                                .statusAttributeException(possibleTemporaryException.get()).create());
+                    // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                    // right error code so that the client knows to retry
+                    final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+                    if (possibleSpecialException.isPresent()) {
+                        final Throwable special = possibleSpecialException.get();
+                        final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                                statusMessage(special.getMessage()).
+                                statusAttributeException(special);
+                        if (special instanceof TemporaryException) {
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                        } else if (special instanceof Failure) {
+                            final Failure failure = (Failure) special;
+                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                    statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                        }
+                        context.writeAndFlush(specialResponseMsg.create());
                     } else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
                         final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
                         logger.warn(errorMessage);
@@ -244,11 +254,22 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
                     onError(graph, context);
                 }
             } catch (Exception ex) {
-                final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
-                if (possibleTemporaryException.isPresent()) {
-                    context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
-                            .statusMessage(possibleTemporaryException.get().getMessage())
-                            .statusAttributeException(possibleTemporaryException.get()).create());
+                // if any exception in the chain is TemporaryException or Failure then we should respond with the
+                // right error code so that the client knows to retry
+                final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+                if (possibleSpecialException.isPresent()) {
+                    final Throwable special = possibleSpecialException.get();
+                    final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+                            statusMessage(special.getMessage()).
+                            statusAttributeException(special);
+                    if (special instanceof TemporaryException) {
+                        specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+                    } else if (special instanceof Failure) {
+                        final Failure failure = (Failure) special;
+                        specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+                                statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                    }
+                    context.writeAndFlush(specialResponseMsg.create());
                 } else {
                     logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
                     context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
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 2f88ddc..dd9b682 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
@@ -1083,4 +1083,20 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
             assertEquals(ResponseStatusCode.SERVER_ERROR_TEMPORARY, ((ResponseException) t).getResponseStatusCode());
         }
     }
+
+    @Test
+    public void shouldGenerateFailureErrorResponseStatusCode() throws Exception {
+        final Cluster cluster = TestClientFactory.build().create();
+        final Client client = cluster.connect();
+
+        try {
+            client.submit("g.inject(0).fail('make it stop')").all().get();
+            fail("Should have tanked since we used fail() step");
+        } catch (Exception ex) {
+            final Throwable t = ex.getCause();
+            assertThat(t, instanceOf(ResponseException.class));
+            assertEquals("make it stop", t.getMessage());
+            assertEquals(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, ((ResponseException) t).getResponseStatusCode());
+        }
+    }
 }
diff --git a/gremlin-test/features/sideEffect/Fail.feature b/gremlin-test/features/sideEffect/Fail.feature
new file mode 100644
index 0000000..53f2d56
--- /dev/null
+++ b/gremlin-test/features/sideEffect/Fail.feature
@@ -0,0 +1,46 @@
+# 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.
+
+@StepClassSideEffect @StepFail
+Feature: Step - fail()
+
+  Scenario: g_V_fail
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().fail()
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  Scenario: g_V_failXmsgX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().fail("msg")
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  Scenario: g_V_unionXout_failX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().union(out(), fail())
+      """
+    When iterated to list
+    Then the traversal will raise an error
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
index 6b644a0..daeff68 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -53,6 +53,7 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
 import static org.hamcrest.core.Every.everyItem;
 import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
 import java.math.BigDecimal;
@@ -81,6 +82,7 @@ public final class StepDefinition {
     private final Map<String, String> stringParameters = new HashMap<>();
     private Traversal traversal;
     private Object result;
+    private Throwable error;
     private static final Pattern edgeTripletPattern = Pattern.compile("(.+)-(.+)->(.+)");
     private static final Pattern ioPattern = Pattern.compile("g\\.io\\(\"(.*)\"\\).*");
     private List<Pair<Pattern, Function<String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, Function<String,String>>>() {{
@@ -204,6 +206,7 @@ public final class StepDefinition {
         }
 
         if (result != null) result = null;
+        if (error != null) error = null;
     }
 
     @After
@@ -243,16 +246,26 @@ public final class StepDefinition {
 
     @When("iterated to list")
     public void iteratedToList() {
-        result = traversal.toList();
+        try {
+            result = traversal.toList();
+        } catch (Exception ex) {
+            error = ex;
+        }
     }
 
     @When("iterated next")
     public void iteratedNext() {
-        result = traversal.next();
+        try {
+            result = traversal.next();
+        } catch (Exception ex) {
+            error = ex;
+        }
     }
 
     @Then("the result should be unordered")
     public void theResultShouldBeUnordered(final DataTable dataTable) {
+        assertThatNoErrorWasThrown();
+
         final List<Object> actual = translateResultsToActual();
 
         // account for header in dataTable size
@@ -265,6 +278,8 @@ public final class StepDefinition {
 
     @Then("the result should be ordered")
     public void theResultShouldBeOrdered(final DataTable dataTable) {
+        assertThatNoErrorWasThrown();
+
         final List<Object> actual = translateResultsToActual();
 
         // account for header in dataTable size
@@ -277,6 +292,9 @@ public final class StepDefinition {
 
     @Then("the result should be of")
     public void theResultShouldBeOf(final DataTable dataTable) {
+        assertThatNoErrorWasThrown();
+
+
         final List<Object> actual = translateResultsToActual();
 
         // skip the header in the dataTable
@@ -286,6 +304,8 @@ public final class StepDefinition {
 
     @Then("the result should have a count of {int}")
     public void theResultShouldHaveACountOf(final Integer val) {
+        assertThatNoErrorWasThrown();
+
         if (result instanceof Iterable)
             assertEquals(val.intValue(), IteratorUtils.count((Iterable) result));
         else if (result instanceof Map)
@@ -296,15 +316,27 @@ public final class StepDefinition {
 
     @Then("the graph should return {int} for count of {string}")
     public void theGraphShouldReturnForCountOf(final Integer count, final String gremlin) {
+        assertThatNoErrorWasThrown();
+
         assertEquals(count.longValue(), ((GraphTraversal) parseGremlin(applyParameters(gremlin))).count().next());
     }
 
     @Then("the result should be empty")
     public void theResultShouldBeEmpty() {
+        assertThatNoErrorWasThrown();
+
         assertThat(result, instanceOf(Collection.class));
         assertEquals(0, IteratorUtils.count((Collection) result));
     }
 
+    @Then("the traversal will raise an error")
+    public void theTraversalWillRaiseAnError() {
+        assertNotNull(error);
+
+        // consume the error now that it has been asserted
+        error = null;
+    }
+
     //////////////////////////////////////////////
 
     @Given("an unsupported test")
@@ -319,6 +351,10 @@ public final class StepDefinition {
 
     //////////////////////////////////////////////
 
+    private void assertThatNoErrorWasThrown() {
+        if (error != null) throw new RuntimeException(error);
+    }
+
     private Traversal parseGremlin(final String script) {
         final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script));
         final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer));