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:05 UTC

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

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));