Merge branch '3.4-dev'
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 1f77b50..9c18e07 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -117,6 +117,9 @@
 * Allowed Gremlin Python sugar calls from anonymous context.
 * Implemented `AutoCloseable` on `MultiIterator`.
 * Fixed an iterator leak in `HasContainer`.
+* Fixed bug in `:bytecode` command preventing translations with whitespace from working properly.
+* Added `reset` and `config` options to the `:bytecode` command to allow for greater customization options.
+* Added GraphSON extension module and the `TinkerIoRegistry` to the default `GraphSONMapper` configuration used by the `:bytecode` command.
 * Added `GremlinASTChecker` to provide a way to extract properties of scripts before doing an actual `eval()`.
 * Avoided creating unnecessary detached objects in JVM.
 * Added support for `TraversalStrategy` usage in Javascript.
diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc
index 5fd2be0..c4c2764 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -148,10 +148,29 @@
 ==>{"@type":"g:Bytecode","@value":{"step":[["V"],["out","knows"]]}}
 gremlin> :bytecode translate g {"@type":"g:Bytecode","@value":{"step":[["V"],["out","knows"]]}} <2>
 ==>g.V().out("knows")
+gremlin> m = GraphSONMapper.build().create()
+==>org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper@69d6a7cd
+gremlin> :bc config m  <3>
+==>Configured bytecode serializer
+gremlin> :bc from g.V().property('d',java.time.YearMonth.now()) <4>
+Could not find a type identifier for the class : class java.time.Month. Make sure the value to serialize has a type identifier registered for its class. (through reference chain: java.time.YearMonth["month"])
+Type ':help' or ':h' for help.
+Display stack trace? [yN]n
+gremlin> :bc reset  <5>
+==>Bytecode serializer reset to GraphSON 3.0 with extensions and TinkerGraph serializers
+gremlin> :bc from g.V().property('d',java.time.YearMonth.now())
+==>{"@type":"g:Bytecode","@value":{"step":[["V"],["property","d",{"@type":"gx:YearMonth","@value":"2020-11"}]]}}
 ----
 
 <1> Generates a GraphSON 3.0 representation of the traversal as bytecode.
 <2> Converts bytecode in GraphSON 3.0 format to a traversal string.
+<3> Configure a custom `GraphSONMapper` for the `:bytecode` command to use which can be helpful when working with
+custom classes from different graph providers. The `config` option can take a `GraphSONMapper` argument as shown or
+one or more `IoRegistry` or `SimpleModule` implementations that will plug into the default `GraphSONMapper` constructed
+by the `:bytecode` command. The default will configure for GraphSON 3.0 with the extensions module and, if present,
+the `TinkerIoRegistry` from TinkerGraph.
+<4> Note that the `YearMonth` will not serialize because `m` did not configure the extensions module.
+<5> After `reset` it works properly once more.
 
 NOTE: The Console does expose the `:record` command which is inherited from the Groovy Shell. This command works well
 with local commands, but may record session outputs differently for `:remote` commands. If there is a need to use
diff --git a/docs/src/upgrade/release-3.4.x.asciidoc b/docs/src/upgrade/release-3.4.x.asciidoc
index 9799577..ab095bc 100644
--- a/docs/src/upgrade/release-3.4.x.asciidoc
+++ b/docs/src/upgrade/release-3.4.x.asciidoc
@@ -70,6 +70,16 @@
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2461[TINKERPOP-2461]
 
+==== Bytecode Command Improvements
+
+The `:bytecode` command in the Gremlin console includes two new options: `reset` and `config`. Both options provide
+ways to better control the `GraphSONMapper` used internally by the command. The `reset` option will replace the current
+`GraphSONMapper` with a new one with some basic defaults: GraphSON 3.0 with extension and `TinkerIoRegistry` if
+present. The `config` option provides a way to specify a custom `GraphSONMapper` or additional configurations to the
+default one previously described.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2479[TINKERPOP-2479]
+
 ==== withStrategies() Groovy Syntax
 
 The `withStrategies()` configuration step accepts a variable number of `TraversalStrategy` instances. In Java, those
diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.groovy
index 93e28eb..0d739fc 100644
--- a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.groovy
+++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.groovy
@@ -22,9 +22,12 @@
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal
 import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator
+import org.apache.tinkerpop.gremlin.structure.io.IoRegistry
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV3d0
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper
+import org.apache.tinkerpop.shaded.jackson.databind.module.SimpleModule
 import org.apache.groovy.groovysh.ComplexCommandSupport
 import org.apache.groovy.groovysh.Groovysh
 
@@ -37,16 +40,27 @@
 
     private final Mediator mediator
 
-    private final ObjectMapper mapper = GraphSONMapper.build().version(GraphSONVersion.V3_0).create().createMapper()
+    private ObjectMapper mapper
 
     public BytecodeCommand(final Groovysh shell, final Mediator mediator) {
-        super(shell, ":bytecode", ":bc", ["from", "translate"])
+        super(shell, ":bytecode", ":bc", ["config", "from", "reset", "translate"])
         this.mediator = mediator
+        do_reset()
+    }
+
+    def Object do_config = { List<String> arguments ->
+        def resolvedArgs = arguments.collect{shell.interp.context.getVariable(it)}
+        try {
+            this.initMapper(resolvedArgs)
+            return "Configured bytecode serializer"
+        } catch (IllegalArgumentException iae) {
+            return iae.message
+        }
     }
     
     def Object do_from = { List<String> arguments ->
         mediator.showShellEvaluationOutput(false)
-        def args = arguments.join("")
+        def args = arguments.join(" ")
         def traversal = args.startsWith("@") ? shell.interp.context.getVariable(args.substring(1)) : shell.execute(args)
         if (!(traversal instanceof Traversal))
             return "Argument does not resolve to a Traversal instance, was: " + traversal.class.simpleName
@@ -57,8 +71,56 @@
 
     def Object do_translate = { List<String> arguments ->
         def g = arguments[0]
-        def args = arguments.drop(1).join("")
+        def args = arguments.drop(1).join(" ")
         def graphson = args.startsWith("@") ? shell.interp.context.getVariable(args.substring(1)) : args
         return GroovyTranslator.of(g).translate(mapper.readValue(graphson, Bytecode.class))
     }
+
+    def Object do_reset = { List<String> arguments ->
+        def (GraphSONMapper.Builder builder, boolean loadedTinkerGraph) = createDefaultBuilder()
+
+        try {
+            this.initMapper([builder.create()])
+            return "Bytecode serializer reset to GraphSON 3.0 with extensions" +
+                    (loadedTinkerGraph ? " and TinkerGraph serializers" : "")
+        } catch (IllegalArgumentException iae) {
+            return iae.message
+        }
+    }
+
+    private def static createDefaultBuilder() {
+        def builder = GraphSONMapper.build().
+                addCustomModule(GraphSONXModuleV3d0.build().create(false)).
+                version(GraphSONVersion.V3_0)
+
+        def loadedTinkerGraph = false
+        try {
+            def tinkergraphIoRegistry = Class.forName("org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0")
+            builder.addRegistry(tinkergraphIoRegistry."instance"())
+            loadedTinkerGraph = true
+        } catch (Exception ignored) {
+            // ok to skip if the registry isn't present
+        }
+        [builder, loadedTinkerGraph]
+    }
+
+    private initMapper(def args) {
+        if (args.size() == 1 && args[0] instanceof GraphSONMapper) {
+            this.mapper = ((GraphSONMapper) args[0]).createMapper()
+        } else {
+            GraphSONMapper.Builder builder = (GraphSONMapper.Builder) createDefaultBuilder()[0]
+            args.each {
+                if (it instanceof GraphSONMapper)
+                    throw new IllegalArgumentException("If specifying a GraphSONMapper it must be the only argument")
+                else if (it instanceof IoRegistry)
+                    builder.addRegistry(it)
+                else if (it instanceof SimpleModule)
+                    builder.addCustomModule(it)
+                else
+                    throw new IllegalArgumentException("Configuration argument of ${it.class.simpleName} is ignored - must be IoRegistry, SimpleModule or a single GraphSONMapper")
+            }
+
+            this.mapper = builder.create().createMapper()
+        }
+    }
 }
diff --git a/gremlin-console/src/main/resources/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.properties b/gremlin-console/src/main/resources/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.properties
index d31bea8..c895855 100644
--- a/gremlin-console/src/main/resources/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.properties
+++ b/gremlin-console/src/main/resources/org/apache/tinkerpop/gremlin/console/commands/BytecodeCommand.properties
@@ -16,5 +16,5 @@
 # under the License.
 
 command.description=Gremlin bytecode helper commands
-command.usage=[from <Traversal>|translate <g> <bytecode>]
+command.usage=[config <[GraphSONMapper|IoRegistry|SimpleModule]>|from <Traversal>|reset|translate <g> <bytecode>]
 command.help=Gremlin bytecode helper commands
\ No newline at end of file
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV2d0.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV2d0.java
index fcd389a..2e48e10 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV2d0.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV2d0.java
@@ -33,7 +33,6 @@
 import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV2d0;
 import org.apache.tinkerpop.shaded.jackson.core.JsonGenerationException;
 import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator;
-import org.apache.tinkerpop.shaded.jackson.core.type.TypeReference;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
 import org.apache.tinkerpop.shaded.jackson.databind.SerializerProvider;
 import org.apache.tinkerpop.shaded.jackson.databind.jsontype.TypeSerializer;