Skip to content

[TINKERPOP 3107] Remove Groovy dependency from default server initialization#3384

Merged
Cole-Greer merged 1 commit into
masterfrom
TINKERPOP-3107
May 7, 2026
Merged

[TINKERPOP 3107] Remove Groovy dependency from default server initialization#3384
Cole-Greer merged 1 commit into
masterfrom
TINKERPOP-3107

Conversation

@Cole-Greer
Copy link
Copy Markdown
Contributor

@Cole-Greer Cole-Greer commented Apr 14, 2026

https://issues.apache.org/jira/browse/TINKERPOP-3107

Gremlin Server currently relies on Groovy init scripts for server initialization — binding traversal sources, running lifecycle hooks, and loading data. This PR introduces a Groovy-free initialization path so that Groovy can be disabled by default in all shipped server configs. Groovy remains available as an
opt-in for backward compatibility.

Changes

Three new YAML configuration mechanisms replace the Groovy init scripts:

Auto-created TraversalSources — After graphs are loaded, a default TraversalSource is automatically created for each graph that doesn't have an explicit traversalSources entry. A graph named graph gets g; others get g_. A minimal config with just a graphs section is now fully functional.

Declarative traversalSources — A new YAML section for explicit TraversalSource creation with optional strategy configuration via a Gremlin query:
yaml
traversalSources: {
g: {graph: graph},
gReadOnly: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)"}}

Java-based lifecycleHooks — A new YAML section for configuring LifeCycleHook implementations via reflection, replacing Groovy-based hook creation:
yaml
lifecycleHooks:

  • className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader
    config: {graph: graph, dataset: modern}

Supporting changes

  • TinkerFactoryDataLoader — built-in LifeCycleHook that loads TinkerFactory sample datasets (modern, classic, crew, grateful, sink, airroutes) without Groovy
  • LifeCycleHook interface — added default void init(Map) for configuration and GraphManager to Context
  • Settings — new TraversalSourceSettings and LifeCycleHookSettings config classes with SnakeYAML type descriptors
  • ServerTestHelper — null-safe for configs without gremlin-groovy in scriptEngines
  • Deprecated the Groovy-based LifeCycleHook/TraversalSource creation path with startup warnings
  • Script engine allowlist — GremlinExecutor now restricts available script engines to those
    explicitly listed in scriptEngines (plus gremlin-lang, which is always permitted).
    Requests targeting an engine not in the allowlist are rejected. This prevents clients from
    invoking gremlin-groovy when it is removed from the default configuration.
  • New gremlin-server-airroutes.yaml shipped config

Config migrations

All default configs under gremlin-server/conf/ updated to remove gremlin-groovy from scriptEngines. All Docker integration test configs (docker/gremlin-server/) and JVM test configs migrated to use traversalSources + lifecycleHooks instead of generate-all.groovy. The gremlin-console test infrastructure
similarly migrated.

Deleted files

  • gremlin-server/src/test/scripts/generate-all.groovy — replaced by YAML config
  • gremlin-server/src/test/scripts/test-server-start.groovy — dead code
  • gremlin-console/src/test/resources/.../generate.groovy — replaced by YAML config

Testing

  • TinkerFactoryDataLoaderTest — all 6 datasets, error cases (missing graph, non-TinkerGraph, unknown dataset, missing config)
  • ServerGremlinExecutorTest — auto-creation, explicit suppression, lifecycle hook instantiation, resolveLanguage logic
  • SettingsTest — YAML parsing for traversalSources and lifecycleHooks, defaults when absent
  • GremlinServerConfigIntegrateTest — parameterized smoke test that boots each shipped config and runs a query
  • Existing JVM integration tests exercise the migrated gremlin-server-integration.yaml
  • GLV integration tests exercise the migrated Docker configs

VOTE +1

Comment thread docs/src/reference/gremlin-applications.asciidoc Outdated
Comment thread docs/src/reference/gremlin-applications.asciidoc Outdated
Comment thread docs/src/reference/gremlin-applications.asciidoc Outdated
Comment thread docs/src/upgrade/release-4.x.x.asciidoc Outdated
@vkagamlyk
Copy link
Copy Markdown
Contributor

Can you add unit test to verify gremlin-groovy is disabled? something like

 @Test
    public void gremlinLang() {
        final Cluster cluster = Cluster.build().addContactPoint("localhost").port(8182).create();
        final Client client = cluster.connect();

        // should handle traversal
        final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster, "gmodern"));
        long count = g.V().count().next();
        assertEquals(6, count);

        // should handle script
        final RequestOptions noLang = RequestOptions.build().addAlias("g", "gmodern").create();
        count = client.submit("g.V().count().next()", noLang).one().getLong();
        assertEquals(6, count);

        // should handle script with explicit engine name
        final RequestOptions requestOptions = RequestOptions.build().language("gremlin-lang").addAlias("g", "gmodern").create();
        count = client.submit("g.V().count().next()", requestOptions).one().getLong();
        assertEquals(6, count);

        // should not allow suspicious queries
        try {
            client.submit("2+2", requestOptions).one().getLong();
            fail("should throw exception");
        } catch (final CompletionException e) {
            final Throwable inner = e.getCause();
            assertTrue(inner instanceof ResponseException);
            assertEquals(ResponseStatusCode.SERVER_ERROR_EVALUATION, ((ResponseException) inner).getResponseStatusCode());
        }

        // in gremlin-groovy '1g' is valid BigDecimal value, but in gremlin-lang should be '1m';
        // the easiest way to determine which script engine the request was executed on
        try {
            client.submit("g.inject(1g)", requestOptions).one().getLong();
            fail("should throw exception");
        } catch (final CompletionException e) {
            final Throwable inner = e.getCause();
            assertTrue(inner instanceof ResponseException);
            assertEquals(ResponseStatusCode.SERVER_ERROR_EVALUATION, ((ResponseException) inner).getResponseStatusCode());
        }

        final BigDecimal one = (BigDecimal)client.submit("g.inject(1m)", requestOptions).one().getObject();
        assertEquals(BigDecimal.ONE, one);
    }

@Cole-Greer
Copy link
Copy Markdown
Contributor Author

Can you add unit test to verify gremlin-groovy is disabled? something like

 @Test
    public void gremlinLang() {
        final Cluster cluster = Cluster.build().addContactPoint("localhost").port(8182).create();
        final Client client = cluster.connect();

        // should handle traversal
        final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster, "gmodern"));
        long count = g.V().count().next();
        assertEquals(6, count);

        // should handle script
        final RequestOptions noLang = RequestOptions.build().addAlias("g", "gmodern").create();
        count = client.submit("g.V().count().next()", noLang).one().getLong();
        assertEquals(6, count);

        // should handle script with explicit engine name
        final RequestOptions requestOptions = RequestOptions.build().language("gremlin-lang").addAlias("g", "gmodern").create();
        count = client.submit("g.V().count().next()", requestOptions).one().getLong();
        assertEquals(6, count);

        // should not allow suspicious queries
        try {
            client.submit("2+2", requestOptions).one().getLong();
            fail("should throw exception");
        } catch (final CompletionException e) {
            final Throwable inner = e.getCause();
            assertTrue(inner instanceof ResponseException);
            assertEquals(ResponseStatusCode.SERVER_ERROR_EVALUATION, ((ResponseException) inner).getResponseStatusCode());
        }

        // in gremlin-groovy '1g' is valid BigDecimal value, but in gremlin-lang should be '1m';
        // the easiest way to determine which script engine the request was executed on
        try {
            client.submit("g.inject(1g)", requestOptions).one().getLong();
            fail("should throw exception");
        } catch (final CompletionException e) {
            final Throwable inner = e.getCause();
            assertTrue(inner instanceof ResponseException);
            assertEquals(ResponseStatusCode.SERVER_ERROR_EVALUATION, ((ResponseException) inner).getResponseStatusCode());
        }

        final BigDecimal one = (BigDecimal)client.submit("g.inject(1m)", requestOptions).one().getObject();
        assertEquals(BigDecimal.ONE, one);
    }

I've adapted this test and added it to the server integration tests. I've given it extra cases to additionally verify error responses if the driver explicitly asks for language: "gremlin-groovy" when it is not configured (based on a new script engine allowlist), as well as if the driver asks for a completely unknown language.

Comment thread docs/src/reference/gremlin-applications.asciidoc
Comment thread docker/gremlin-server/gremlin-server-integration-krb5.yaml Outdated
@kenhuuu
Copy link
Copy Markdown
Contributor

kenhuuu commented Apr 29, 2026

A lot of the test files do something like System.getProperty("build.dir"). Shouldn't this use something like TestSupport.getRootOfBuildDirectory()? Otherwise, how would I run these tests through my IDE? It would only work with Maven?

case "sink":
TinkerFactory.generateKitchenSink(tinkerGraph);
break;
case "airroutes":
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this take into account the min-jar build that won't come with the kryo data for air-routes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not explicitly handled here, but it implicitly behaves in the way I would expect. TinkerFactory.generateAirRoutes() already throws an IllegalStateException with a clear message about the min classifier if the file is not found. That error will propagate through the hook and ultimately cause the server to fail to start. I think this is the safest way of handling this case.

@vkagamlyk
Copy link
Copy Markdown
Contributor

VOTE +1

…ation

Gremlin Server currently relies on Groovy init scripts for server
initialization — binding traversal sources, running lifecycle hooks,
and loading data. This introduces a Groovy-free initialization path
so that Groovy can be disabled by default in all shipped server configs.
Groovy remains available as an opt-in for backward compatibility.

Three new YAML configuration mechanisms replace the Groovy init scripts:

Auto-created TraversalSources — After graphs are loaded, a default
TraversalSource is automatically created for each graph that doesn't
have an explicit traversalSources entry. A graph named "graph" gets
"g"; others get "g_<name>".

Declarative traversalSources — A new YAML section for explicit
TraversalSource creation with optional strategy configuration:

  traversalSources:
    g: {graph: graph}
    gReadOnly: {graph: graph, gremlinExpression: "g.withStrategies(ReadOnlyStrategy)"}

Java-based lifecycleHooks — A new YAML section for configuring
LifeCycleHook implementations via reflection:

  lifecycleHooks:
    - className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader
      config: {graph: graph, dataset: modern}

Supporting changes:
- TinkerFactoryDataLoader: built-in LifeCycleHook that loads TinkerFactory
  sample datasets (modern, classic, crew, grateful, sink, airroutes)
- LifeCycleHook interface: added default init(Map) and GraphManager to Context
- Settings: new TraversalSourceSettings and LifeCycleHookSettings classes
- Script engine allowlist: GremlinExecutor restricts available engines to
  those in scriptEngines config (plus gremlin-lang, always permitted)
- Deprecated Groovy-based LifeCycleHook/TraversalSource creation with
  startup warnings
- New gremlin-server-airroutes.yaml shipped config

All default configs updated to remove gremlin-groovy from scriptEngines.
Docker and JVM test configs migrated to use traversalSources +
lifecycleHooks instead of Groovy generate scripts.
@Cole-Greer Cole-Greer merged commit c9a4a75 into master May 7, 2026
40 checks passed
@Cole-Greer Cole-Greer deleted the TINKERPOP-3107 branch May 7, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants