apache / apache/gravitino

[Bug report] bug(catalog-hive): HiveCatalogCapability.caseSensitiveOnName() uses switch-on-enum which generates a synthetic $1 class, causing NoClassDefFoundError when called outside IsolatedClassLoader context

Open Beginner friendly
#11,706 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
3.2k
Forks
935
Avg merge
1d 17h
Merged PRs (30d)
286

Description

### Version

main branch

### Describe what's wrong

### Bug Description

`HiveCatalogCapability.caseSensitiveOnName()` uses a `switch` statement on
`Capability.Scope` (an enum). The Java compiler generates a synthetic helper
class `HiveCatalogCapability$1` for every `switch`-on-enum to maintain a
stable ordinal mapping table.

`caseSensitiveOnName()` is called by `SchemaNormalizeDispatcher` **outside**
the `IsolatedClassLoader.withClassLoader()` boundary:

```
SchemaNormalizeDispatcher.normalizeCaseSensitive()
→ getCapability() → c.capabilities()
→ withClassLoader { catalog.capability() } ← IsolatedClassLoader context; creates HiveCatalogCapability instance
← withClassLoader exits; thread context classloader restored to server classloader
→ applyCaseSensitive() → capabilities.caseSensitiveOnName(Scope.SCHEMA)
→ JVM attempts to load HiveCatalogCapability$1
```

When `HiveCatalogCapability` is correctly loaded by `IsolatedClassLoader`,
its defining classloader is `IsolatedClassLoader`, and `$1` is loaded from
`execJars` successfully. However, in certain classloader initialization timing
windows (e.g. concurrent requests during catalog cache rebuild), `$1` is
requested while the server classloader is involved in the loading chain.
**The JVM permanently caches this load failure**, and all subsequent calls
throw `NoClassDefFoundError` until the process is restarted.

### Root Cause

`SchemaNormalizeDispatcher` calls `Capability.caseSensitiveOnName()` — a
method on a catalog-side implementation — **after** returning from
`withClassLoader`. This is an architectural inconsistency: catalog
implementation code is invoked outside the catalog's isolated classloader
context.

The immediate trigger is the compiler-generated `$1` synthetic class, which
only exists because `switch`-on-enum is used. Replacing `switch` with
`if`-`else` (using `==` on enum constants) eliminates `$1` entirely and makes
`caseSensitiveOnName()` safe to call from any classloader context.

### Proposed Fix

Replace `switch` with `if`-`else` in `HiveCatalogCapability.caseSensitiveOnName()`:

```java
// Before
switch (scope) {
case SCHEMA: case TABLE: case COLUMN:
return CapabilityResult.unsupported("Hive is case insensitive.");
default:
return CapabilityResult.SUPPORTED;
}

// After
if (scope == Scope.SCHEMA || scope == Scope.TABLE || scope == Scope.COLUMN) {
return CapabilityResult.unsupported("Hive is case insensitive.");
}
return CapabilityResult.SUPPORTED;
```

This removes the `$1` synthetic class entirely. `==` on enum constants
compiles to `if_acmpeq` (reference comparison), which has no synthetic class
dependency and is safe across classloader boundaries.

### Additional Context

The same pattern should be reviewed in other `Capability` implementations
across catalogs, as any `switch`-on-enum in a catalog-side class called from
`SchemaNormalizeDispatcher` / `TableNormalizeDispatcher` (outside
`withClassLoader`) would be susceptible to the same issue.

### Error message and/or stacktrace

java.lang.NoClassDefFoundError: org/apache/gravitino/catalog/hive/HiveCatalogCapability$1
at org.apache.gravitino.catalog.hive.HiveCatalogCapability.caseSensitiveOnName(HiveCatalogCapability.java:45)
at org.apache.gravitino.catalog.CapabilityHelpers.applyCaseSensitiveOnName(CapabilityHelpers.java:476)
at org.apache.gravitino.catalog.CapabilityHelpers.applyCaseSensitive(CapabilityHelpers.java:123)
at org.apache.gravitino.catalog.SchemaNormalizeDispatcher.normalizeCaseSensitive(SchemaNormalizeDispatcher.java:94)
at org.apache.gravitino.catalog.SchemaNormalizeDispatcher.loadSchema(SchemaNormalizeDispatcher.java:71)
at org.apache.gravitino.listener.SchemaEventDispatcher.loadSchema(SchemaEventDispatcher.java:116)
...
at org.apache.gravitino.utils.PrincipalUtils.doAs(PrincipalUtils.java:44)

### How to reproduce

+ Which Gravitino version to use
main / any version with Hive catalog

+ Steps
1. Deploy Gravitino with a Hive catalog configured
2. Call any API that goes through SchemaNormalizeDispatcher
(e.g. loadSchema, schemaExists, loadTable on first access):

GET /api/metalakes/{metalake}/catalogs/{hive-catalog}/schemas/{schema}

3. Under specific JVM classloader initialization timing, observe:
NoClassDefFoundError: org/apache/gravitino/catalog/hive/HiveCatalogCapability$1

4. All subsequent requests to the same catalog on this process instance
fail with the same error until restart.

+ Root cause
SchemaNormalizeDispatcher calls capabilities.caseSensitiveOnName() AFTER
IsolatedClassLoader.withClassLoader() exits (i.e. outside the catalog's
isolated classloader context). The switch-on-enum in caseSensitiveOnName()
causes javac to generate HiveCatalogCapability$1. In certain classloader
initialization timing windows the server classloader is involved in the
loading chain and cannot find $1; the JVM permanently caches this failure.

+ Note
The bug is intermittent because it depends on classloader initialization
timing. Once triggered it is permanent within the process lifetime.
Restarting the process clears the cached failure.

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with HiveCatalogCapability.caseSensitiveOnName() and read the call chain through CapabilityHelpers.applyCaseSensitiveOnName(), SchemaNormalizeDispatcher.normalizeCaseSensitive(), and IsolatedClassLoader.withClassLoader(). Replace the enum switch as described, then exercise the Hive catalog API path for loadSchema or schemaExists and confirm the NoClassDefFoundError no longer occurs and the case-sensitivity result remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
databases
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.