Skip to content

refactor(common)!: reorganize hudi-common packages into core and common namespaces#19193

Merged
vinothchandar merged 12 commits into
apache:masterfrom
vinothchandar:code-sweep-hudi-common
Jul 6, 2026
Merged

refactor(common)!: reorganize hudi-common packages into core and common namespaces#19193
vinothchandar merged 12 commits into
apache:masterfrom
vinothchandar:code-sweep-hudi-common

Conversation

@vinothchandar

@vinothchandar vinothchandar commented Jul 5, 2026

Copy link
Copy Markdown
Member

Describe the issue this Pull Request addresses

hudi-common has accumulated classes in packages that no longer reflect their role: engine-facing domain machinery sitting in the root org.apache.hudi package, shared substrate under a top-level internal.* namespace, write-client config classes outside the common config namespace, and lock/validator/file-IO contracts under org.apache.hudi.client.*/org.apache.hudi.io.* where they collide conceptually with other modules' namespaces.

This PR establishes a deliberate two-tier package taxonomy inside hudi-common, without changing the module structure:

  • org.apache.hudi.core.* — domain-aware code implementing core read/write behavior (depends on both hudi-io primitives and Hudi domain models).
  • org.apache.hudi.common.* — shared substrate used across all modules and engines (type system, expressions, configs, utilities), with no domain awareness.

This pre-stages a future hudi-common/hudi-core module split so that the eventual module move is FQN-stable, and removes one piece of dead code found along the way.

Summary and Changelog

No behavior changes; this is a package reorganization (423 files, overwhelmingly import-line churn). One commit per relocation:

  • refactor(common): InProcessLockProvider, NoopLockProvider moved to o.a.h.core.transaction.lock. Because the old InProcessLockProvider FQN is a documented hoodie.write.lock.provider config value resolved via reflection, a deprecated stub is kept at the old FQN, marked with a new @CompatAlias(of, since) annotation (added next to @PublicAPIClass in hudi-io). The three sites that string-compare the configured provider class (clock-skew inference, MDT multi-writer guard, single-writer auto-adjust) now accept both old and new FQNs via one predicate.
  • refactor(common)!: BasePreCommitValidator, ValidationContext moved to o.a.h.core.validator.
  • refactor(common)!: 7 metrics config classes moved to o.a.h.common.config.metrics, joining HoodieCommonConfig/HoodieMetadataConfig; avoids a split package with hudi-io's o.a.h.common.metrics.
  • refactor(common)!: 19 file I/O contract classes (HoodieFileReader/Writer and factories, HoodieIOFactory, HFile/bootstrap readers, HoodieParquetConfigInjector) moved to o.a.h.core.io[.storage]. Engine implementations (o.a.h.io.storage.hadoop.*, Spark/Flink packages) do not move.
  • refactor(common)!: removed ByteBufferBackedInputFile and its o.a.h.parquet.io package — zero references repo-wide (orphaned by an earlier parquet log-block refactor).
  • refactor(common)!: ParquetAdapter moved to o.a.h.stats, next to its only consumers.
  • refactor(common)!: BaseHoodieTableFileIndex moved to o.a.h.core.read.
  • refactor(common)!: expression package moved to o.a.h.common.expression.
  • refactor(common)!: internal.schema (schema evolution type system, 21 classes) moved to o.a.h.common.schema.internal, nested under the existing o.a.h.common.schema.
  • fix(trino): hudi-trino-plugin was excluded from the sweep — it compiles against released Hudi 1.0.2, not the in-repo modules; its imports update when dep.hudi.version is bumped.

The moves surfaced several hidden same-package couplings, now made explicit: spark-common scala classes importing the file-index base explicitly instead of bare same-package references; HoodieBaseRelation using the metadataConfig getter instead of cross-package protected field access; two read-only file-index introspection methods made public; a validator test accessing protected state through an accessor on its mock. No code was copied.

Impact

No config keys, defaults, or semantics change. No storage-format impact: none of the moved FQNs are persisted (hoodie.properties, commit metadata, and serialized schemas carry no Java class names), verified per move.

Breaking (source/binary) for externally compiled code only, always failing loudly (NoClassDefFoundError/compile error), never silently:

  • Custom pre-commit validators extending BasePreCommitValidator (EVOLVING API, shipped only in 1.2.0; the hoodie.precommit.validators config names the user's own class, which is unchanged).
  • Programmatic construction of metrics configs (withMetricsConfig(..) builders, .key() constants). Property-based metrics configuration is unaffected; reporter selection is enum-based.
  • Custom HoodieIOFactory subclasses / HoodieParquetConfigInjector implementations (both EVOLVING-era plug points; the config values name the user's or shipped implementation class, unchanged — e.g. default org.apache.hudi.io.storage.hadoop.HoodieHadoopIOFactory).
  • Direct imports of BaseHoodieTableFileIndex (engine subclass FQNs HiveHoodieTableFileIndex/SparkHoodieTableFileIndex are unchanged and bundles are self-consistent, so existing Presto/Trino deployments are unaffected at runtime), Hudi Expression construction, and InternalSchema/SerDeHelper usage in custom integrations.

Users configuring Hudi via hoodie.* properties see zero impact; existing configs carrying the old InProcessLockProvider FQN keep working via the compatibility stub. No performance impact.

Risk Level

low — mechanical relocations with compatibility handling where user configs are involved. Verification: full 52-module reactor build green (-Dspark4.1 -Dflink2.1, checkstyle/scalastyle enforced); ~330 targeted unit tests across all moved areas (lock provider incl. reflective load of the legacy FQN, validators across client-common/utilities/flink, metrics configs/reporters, HFile reader/writer factories, file index, expressions, full schema-evolution suites); functional verification via TestHoodieClientMultiWriter (47 tests, concurrent writers through LockManager) and transaction/lock-manager/time-generator suites covering every reflection path touched.

Documentation Update

none — no config keys or defaults change. The config reference regenerates class-location mentions automatically (doc strings referencing moved FQNs were updated in-repo).

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

@github-actions github-actions Bot added the size:XL PR with lines of changes > 1000 label Jul 5, 2026
@vinothchandar

Copy link
Copy Markdown
Member Author

No code changes, just classes being moved around packages for a better grouping. Specifically, I am splitting classes into core (hudi domain aware classes) and common (just common shared code). This PR lays ground work. the split is not fully done.

…tion.lock

Relocates InProcessLockProvider and NoopLockProvider out of
org.apache.hudi.client.transaction.lock in hudi-common, pre-staging the
future hudi-common/hudi-core module split so the eventual module move
stays FQN-stable.

- InProcessLockProvider: the old FQN is a documented, user-configured
  value of hoodie.write.lock.provider resolved via reflection, so a
  deprecated stub is kept at the old FQN, marked with the new
  @CompatAlias annotation (hudi-io) recording the replacement and the
  1.3.0 deprecation release. The stub preserves the reflective
  (LockConfiguration, StorageConfiguration) constructor contract.
- NoopLockProvider: internal-only (upgrade/downgrade nested-lock path),
  moved without a stub.
- Sites that string-compare the configured lock provider class now use
  InProcessLockProvider.isInProcessLockProvider(), which accepts both
  the current and legacy FQN: the 1ms clock-skew inference in
  HoodieTimeGeneratorConfig, the metadata-table multi-writer guard in
  HoodieMetadataWriteUtils, and the single-writer auto-adjust in
  HoodieWriteConfig. Without this, configs carrying the legacy FQN
  would silently lose those behaviors.

Behavior-preserving otherwise. Legacy-FQN test configs are deliberately
left in place as regression coverage for the stub path; a reflective
load test and a legacy-FQN clock-skew row are added.
…pache.hudi.core.validator

Relocates BasePreCommitValidator and ValidationContext from
org.apache.hudi.client.validator to org.apache.hudi.core.validator in
hudi-common, continuing the core-package staging for the future
hudi-common/hudi-core module split.

BREAKING CHANGE: both classes shipped in 1.2.0 as
@PublicAPIClass(EVOLVING) and are the extension point for custom
pre-commit validators on the Flink path (hoodie.precommit.validators).
A custom validator compiled against 1.2.0 fails at load with
NoClassDefFoundError and must be recompiled with the new import. This
is an accepted EVOLVING-API change: the config only ever names the
user's concrete class (FQN unchanged), all shipped validators move
together, and no compatibility stub is provided deliberately - a stub
at the old FQN would turn externally compiled overrides of
validateWithMetadata(ValidationContext) into dead overloads once the
parameter type moves, silently skipping the user's validation, which
is worse than the loud load failure.

Engine subclasses and utils in hudi-client-common, hudi-utilities and
hudi-flink update their imports; StreamingOffsetValidator and its test
previously resolved the base types via same-package access and now
import them explicitly. The test also accessed the protected config
field cross-package, which the move surfaced; it now goes through an
accessor on the test's mock subclass.

Verified with the client-common, utilities and flink validator suites.
…mon.config.metrics

Relocates the seven metrics config classes (HoodieMetricsConfig and the
CloudWatch/Datadog/Graphite/Jmx/M3/Prometheus variants) from
org.apache.hudi.config.metrics to org.apache.hudi.common.config.metrics,
joining HoodieCommonConfig and HoodieMetadataConfig in the hudi-common
config namespace. The old package was a leftover from the classes'
original hudi-client-common home; the new location also avoids adding
a split package under hudi-io's org.apache.hudi.common.metrics.

BREAKING CHANGE: source/binary break for code that imports these
classes to build configs programmatically (e.g.
HoodieWriteConfig.Builder#withMetricsConfig) - fails loudly with
NoClassDefFoundError, fixed by an import change. Regular usage is
unaffected: all hoodie.metrics.* config keys are unchanged, reporter
selection is enum-based (no class names in config values), and custom
reporters via hoodie.metrics.reporter.class are instantiated with a
(Properties, MetricRegistry) constructor that never exposes these
types. No compatibility stub: inherited static builder methods would
fail exact-descriptor resolution on the nested Builder types anyway.
Note: mixing a new engine bundle with an older hudi-aws-bundle fails
at metrics init for CloudWatch (reflective constructor lookup); bundle
versions must match, as already required.

Verified across hudi-common, client-common, spark/java clients,
hudi-spark, utilities, sync-common, hive-sync and hudi-aws with the
metrics config/reporter test suites.
…re.io

Relocates the 19 domain-aware file I/O contract classes in hudi-common
from org.apache.hudi.io[.storage] to org.apache.hudi.core.io[.storage]:
HoodieFileReader/Writer and their factories, HoodieIOFactory,
HFile/bootstrap readers, HoodieParquetConfigInjector, and the ORC/HFile
config holders. This establishes org.apache.hudi.core as the namespace
for upper-layer code that depends on both hudi-io primitives and
hudi-common domain types (HoodieRecord, HoodieSchema), disambiguating
it from the hudi-io module's low-level org.apache.hudi.io namespace.
Engine implementations (org.apache.hudi.io.storage.hadoop and the
Spark/Flink packages) do not move; hoodie.io.factory.class defaults
and values are unchanged.

BREAKING CHANGE: source/binary break for externally compiled
HoodieIOFactory subclasses (@PublicAPIClass EVOLVING, plugged via
hoodie.io.factory.class) and HoodieParquetConfigInjector
implementations (hoodie.parquet.write.config.injector.class, since
1.2.0) - both fail loudly at load with NoClassDefFoundError and are
fixed by an import change plus recompile. Config values themselves are
untouched in both cases (they name the user's or the shipped
implementation class). Regular property-based usage is unaffected; no
config key, reflection, serialization or bundle surface changes.

Same-package consumers of the moved contracts (Spark reader/writer
classes remaining in org.apache.hudi.io.storage, and tests) now import
them explicitly. The hudi-trino-plugin import updates are verified by
its own CI (module is not in the default reactor), as is the
hudi-integ-test import and the non-active spark version modules.

Verified with -Dspark4.1 across common, hadoop-common, client-common,
spark/java clients, hudi-spark, hadoop-mr and utilities, and with
-Dflink2.1 across the flink modules; HFile reader/factory, file writer
factory and parquet config injector suites pass.
Deletes org.apache.hudi.parquet.io.ByteBufferBackedInputFile and with
it the org.apache.hudi.parquet.io package, which contained nothing
else. The class has zero references in the repository - orphaned by an
earlier refactor of the parquet log-block read path.

BREAKING CHANGE: the class shipped in releases up to 1.2.0, so
externally compiled code importing this helper would fail loudly at
class load. It is a 30-line self-contained utility; any such user can
inline it. Never referenced by any config value or reflection path.
Relocates org.apache.hudi.ParquetAdapter to org.apache.hudi.stats,
the package of its only two consumers (ValueType, ValueMetadata). The
interface is a parquet-version compatibility shim serving column-stats
typing exclusively; its two implementations remain in
org.apache.parquet.schema (deliberate namespace residency for parquet
API access) and are loaded reflectively via class literals, so they
follow the move automatically.

BREAKING CHANGE: source/binary break for external code importing this
interface - unlikely in practice, since the static getAdapter() factory
hard-wires the only two shipped implementations. Fails loudly at class
load if it exists anywhere; never referenced by config or reflection
by name.
…ore.read

Relocates the engine-agnostic file index base (and its unit test) from
the org.apache.hudi root package to org.apache.hudi.core.read,
establishing the read-side home in the core namespace for domain-aware
query/listing abstractions (Phase 4 read-path work lands alongside).
It is a consumer of the metadata subsystem, not part of it, so
org.apache.hudi.metadata was deliberately not chosen.

BREAKING CHANGE: source/binary break for external code importing
BaseHoodieTableFileIndex or its nested types (PartitionPath,
FileStatusCache) directly. Engine subclasses shipped by Hudi keep
their FQNs (HiveHoodieTableFileIndex, SparkHoodieTableFileIndex), and
engine bundles are self-consistent, so existing Presto/Trino
deployments are unaffected at runtime; connector source trees that
import the base class directly see a compile-time import fix at their
next Hudi version bump. Never a config value or reflection target.

The move surfaced several same-package couplings, now explicit: the
spark-common scala classes in package org.apache.hudi (file index and
index-support family) referenced the base bare and now import it,
HoodieBaseRelation read the protected metadataConfig field directly
and now uses the getter, and the read-only introspection methods
getAllQueryPartitionPaths/areAllPartitionPathsCached used by tests
across the old package boundary are now public.

Verified across common, hadoop-common, hadoop-mr, spark modules and
utilities with -Dspark4.1; file index suites pass.
…expression

Relocates the engine-agnostic predicate/expression AST (Expression,
Predicates, Literal, NameReference, bind visitors, StructLike/ArrayData)
from org.apache.hudi.expression to org.apache.hudi.common.expression.
The package has no Hudi domain awareness - its only dependencies are
the internal type system and Option - and its consumers span the read
path, engine bridges and metastore sync tools alike, making it shared
substrate (common) rather than domain machinery (core). Keeps the
dependency layering acyclic for the future module split:
types -> expressions -> domain -> engines.

BREAKING CHANGE: source/binary break for external code constructing
Hudi expressions programmatically, e.g. callers of
HoodieTableMetadata.getPartitionPathWithPathPrefixUsingFilterExpression.
Fails loudly; fixed by an import change. Expression classes are never
named in configs and never loaded reflectively.
…ileIndex

Follow-up to the core.read move: scala brace-group imports name classes
without their package prefix, so the FQN rewrite missed this one test.
Split the moved class out of the brace group.
…ema.internal

Relocates the schema-evolution type system (InternalSchema, Type/Types,
table-change actions, avro converters, visitors, and the file-based
schema storage manager) from org.apache.hudi.internal.schema to
org.apache.hudi.common.schema.internal, nesting it under the existing
org.apache.hudi.common.schema namespace. Like the expression AST that
sits on top of it, this is shared substrate consumed across every
engine and the sync tools - common, not domain core - and "internal"
as a top-level package name was an artifact of its origin.

BREAKING CHANGE: source/binary break for external code importing these
types, e.g. schema-evolution integrations using InternalSchema or
SerDeHelper directly. Fails loudly at compile/class-load; fixed by an
import change. No persisted format impact: serialized schemas (commit
metadata and .schema files) carry no Java class names, and none of
these classes are config values or reflection targets. All engine
integrations (spark, flink, hive, utilities) move in lockstep in-repo.

Verified with the full schema-evolution unit suites (SerDe, avro
evolution, table changes, converter, schema storage manager) across
the spark4.1 chain; version-shim modules for other spark/flink
versions are import-only changes covered by CI.
…released hudi

hudi-trino-plugin is a vendored copy of the Trino connector that
compiles against released Hudi artifacts (dep.hudi.version=1.0.2,
parent io.trino:trino-root), not against the in-repo modules. The
org.apache.hudi.core.io.storage package introduced by the file I/O
contract move does not exist in 1.0.2, so the earlier blanket import
rewrite would have broken the plugin build. Restores the two files to
the released-version imports; the plugin picks up the new package
names whenever dep.hudi.version is bumped to a release containing
them.
CI (TestSevenToEightUpgrade cases with
hoodie.write.lock.provider=org.apache.hudi.client.transaction.lock.NoopLockProvider)
showed the old NoopLockProvider FQN is exercised as a user config value
in the table-upgrade path, invalidating the earlier internal-only
assessment that let it move without an alias. Restores a deprecated
@CompatAlias stub at the old FQN, mirroring the InProcessLockProvider
treatment, and adds a reflective-load regression test covering the
exact LockManager code path that failed.
@vinothchandar vinothchandar force-pushed the code-sweep-hudi-common branch from bbd230a to 518ad60 Compare July 6, 2026 01:48
@hudi-bot

hudi-bot commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@vinothchandar vinothchandar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Made a full pass. mostly import changes outside of the few places where FQN of new lock provider is wired etc.

@vinothchandar

Copy link
Copy Markdown
Member Author

@bhasudha can you please stamp this. CI is passing now

@bhasudha bhasudha left a comment

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.

stamped

@vinothchandar vinothchandar merged commit 3cc8fd1 into apache:master Jul 6, 2026
71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL PR with lines of changes > 1000

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants