Lei Shu created TINKERPOP-3270:
----------------------------------
Summary: `where(P)` throws `ClassCastException` for non-String
predicate values instead of a descriptive validation error
Key: TINKERPOP-3270
URL: https://issues.apache.org/jira/browse/TINKERPOP-3270
Project: TinkerPop
Issue Type: Bug
Reporter: Lei Shu
### TinkerPop version
Reproduced on:
```text
TinkerGraph 3.7.0 — gremlin-server (verified 2026-07-12)
TinkerGraph 3.8.1 — Docker tinkerpop/gremlin-server:latest (image 60673820236b,
built 2026-04-07)
```
Source inspection against the 3.8.1 tag confirms the same unconditional
`(String)` cast is present.
### Environment
* Host OS: Linux
* Query language: Gremlin
* Client: Gremlin Groovy script / gremlin-python WebSocket driver
### Description
`where(P)` accepts a `P<String>` — the predicate value is expected to be a
scope key (a String label for a step or variable binding). Value comparisons
should use `is(P)` instead.
When `where(P)` receives a non-string predicate such as `gt(5)`,
`WherePredicateStep` construction casts `predicate.getValue()` to `String`
without a runtime type check, producing a `ClassCastException`. The failure
occurs during step construction, before any data traversing begins; it does not
crash the Gremlin Server process.
Although the Java generic signature is `where(P<String>)`, dynamically typed
Gremlin language variants and Gremlin Groovy scripts can construct and submit
this traversal at runtime. The raw internal exception obscures the actual API
misuse and makes troubleshooting unnecessarily difficult.
This is not a request to make `where(gt(5))` work as a numeric filter —
`is(gt(5))` is the correct API for value comparisons.
### To reproduce
The simplest trigger applies `where(gt(5))` directly to a value stream:
```groovy
__.values('v').where(gt(5))
// ClassCastException: Integer cannot be cast to String
// at WherePredicateStep.getSelectKey(WherePredicateStep.java:105)
```
The same exception occurs when the invalid `where(P)` appears inside a parent
`where(Traversal)`:
```groovy
g.V().drop()
g.addV('X').property('v',10).iterate()
g.addV('X').property('v',20).iterate()
g.V().hasLabel('X').where(__.values('v').where(gt(5))).values('v').fold()
// ClassCastException — same stack trace
```
### Expected behavior
The invalid argument should be rejected with a descriptive argument or
type-validation exception that identifies the `where(P)` scope-key requirement,
rather than exposing an internal `ClassCastException`.
### Actual behavior
`ClassCastException: class java.lang.Integer cannot be cast to class
java.lang.String`
at `WherePredicateStep.getSelectKey(WherePredicateStep.java:105)`
The exception message gives no indication of what the user did wrong or how to
correct it.
### Root cause
During `WherePredicateStep` construction, `configurePredicates()` calls
`getSelectKey()`, which casts `predicate.getValue()` to `String` without first
validating its runtime type. In 3.8.1 the method has a `Collection` branch as
well, but neither path validates that the value selected as the scope key is a
`String` before casting:
```java
// WherePredicateStep.java (3.8.1)
public String getSelectKey(final P<?> predicate) {
if (predicate.getValue() instanceof Collection) {
// ... existing collection handling ...
return (String) ...;
}
return (String) predicate.getValue(); // line 105 — CCE when value is
Integer
}
```
The cast succeeds when `where(eq('a'))` is used (value = `"a"`, a valid scope
key). It fails when `where(gt(5))` is used (value = `5`, an `Integer`).
### Suggested fix
Validate that the value selected by `getSelectKey()` is a `String` before
casting. The exact validation location and exception type are left to
maintainer discretion.
### Workaround
Use `is()` instead of `where()` for value comparisons:
```groovy
__.values('v').is(gt(5)) // correct
g.V().hasLabel('X').where(__.values('v').is(gt(5))).values('v').fold() // →
[10, 20]
```
### Cross-version verification
| Version | Result |
|---------|--------|
| TinkerGraph 3.7.0 | ClassCastException |
| TinkerGraph 3.8.1 | ClassCastException |
### Impact
This primarily affects diagnostics for dynamically typed clients and remotely
submitted traversals. It does not produce incorrect query results, modify data,
or terminate the Gremlin Server process. A clear workaround (`is(P)`) exists.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)