Apache XTable: one table model, three formats, and a YAML file describing the conversion
Apache XTable (incubating) is a cross-table converter for lakehouse table formats that facilitates interoperability across data processing systems and query engines.
At a glance
- What is it?
- Apache XTable, incubating, converts between Apache Hudi, Apache Iceberg and Delta Lake through a common table representation, so data written in one format can be read by engines that understand another. The bundled jar is driven by a YAML file, and the fiddliest part of that file is the partition spec for Hudi sources.
- Who is it for?
- XTable fits a lakehouse team that cannot pick one table format, since the whole point is writing in one format and letting engines that speak another read it, without maintaining two ingest pipelines per consumer. Skip it if you control the whole stack and can standardise on a single format, because conversion jobs are extra operational surface, and note the version cadence, 0.4.0-incubating from 2026-08-24, means an incubating project with a moving API.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The pitch is interoperability, not another format
The project description calls it a cross-table converter for table formats that facilitates omni-directional interoperability across data processing systems and query engines, currently supporting Apache Hudi, Apache Iceberg and Delta Lake. The mechanism is a common model for table representation: users write data in one format and still benefit from integrations and features available in other formats. Two examples are given, existing Hudi users working with Databricks's Photon Engine, and querying Iceberg tables with Snowflake. The adoption argument is in the second half of the paragraph: creating transformations from one format to another requires implementing a few interfaces, which is what the project expects to make more source and target formats possible over time. Repository topics confirm the three formats it sits between: apache-hudi, apache-iceberg and delta-lake.
Build with Java 11, run the container on 17
The build section says to use Java 11 for building, and suggests jenv if you need another version locally. The commands are Maven wrapper invocations:
./mvnw clean package
./mvnw clean testwith -DskipTests to skip tests while building, a targeted single test through -Dtest=TestDeltaSync -pl xtable-core, and ./mvnw verify for integration tests. One flag is worth internalising before you get a confusing cache: with Maven 3.9 or above the build is cached automatically, and -Dmaven.build.cache.enabled=false turns that off. The Dockerfile tells you what the artifact runs on. It builds with eclipse-temurin:17-jdk-jammy, renames the bundled xtable-utilities jar to app.jar, and runs it on eclipse-temurin:17-jre-jammy as a non privileged user with uid 10001, adding module opens because Spark on Java 17 requires it. So the source level and the runtime differ, which is normal for Java projects and worth knowing before you file a bug about it.
The conversion file is the interface
You get a bundled jar either from a release or from ./mvnw install -DskipTests, and you describe the job in a YAML file. The smallest useful one picks a source format, a list of targets and a list of datasets:
sourceFormat: HUDI
targetFormats:
- DELTA
- ICEBERG
datasets:
-
tableBasePath: s3://tpc-ds-datasets/1GB/hudi/call_center
tableDataPath: s3://tpc-ds-datasets/1GB/hudi/call_center/data
tableName: call_center
namespace: my.dbtableBasePath is the base path of the table and tableDataPath is optional, defaulting to the base path when absent, except for Iceberg source tables where you must give the /data path. namespace is optional and is used when syncing to a catalog. The example dataset set shows the range in one block: an s3 Hudi table with a namespace, an s3 table with a partition spec, a multi-partition table with a timestamp transform, and an abfs path on Azure storage. One conversion file, several datasets, several targets.
partitionSpec is a three field mini language
The partition spec is the field most likely to cost you an afternoon, and the README documents it as a comma separated list of path:type:format entries. path is a dot separated path to the partition field. type describes how the partition value was derived from the column value, with VALUE as an identity transform and YEAR, MONTH, DAY and HOUR as date granularities. format is required when the type is one of the date granularities and gives the date string format as it appears in your file paths. The example shows a Hive style field, cs_sold_date_sk:VALUE, and a multi-partition table using time_millis:DAY:yyyy-MM-dd,type:VALUE. Two constraints are stated plainly: the spec is only required for Hudi source tables, and if the table is not partitioned you leave it blank. Anywhere else you supply it and you are describing a partition layout that the converter did not need, which is a good way to produce a wrong table silently.
Converters are classes, and you can replace them
The default converter implementations can be replaced with custom ones through a second YAML file, keyed by format, naming the class to use for reading and the class to use for writing:
tableFormatConverters:
HUDI:
conversionSourceProviderClass: org.apache.xtable.hudi.HudiConversionSourceProvider
DELTA:
conversionTargetProviderClass: org.apache.xtable.delta.DeltaConversionTarget
configuration:
spark.master: local[2]
spark.app.name: xtableThe comments in that block spell out what is passed to the factory: all user configuration, including Hadoop config and converter specific configuration, is available for instantiation. So this is an extension point with the whole configuration map handed to it, which is how you would plug in a converter for something the project does not support, or swap the Delta converter for your own.
Iceberg catalogs are passed as a separate config file
A catalog can be used when reading and updating Iceberg tables, specified in its own YAML file and passed with the --icebergCatalogConfig option. The format starts with catalogImpl naming the implementation class, catalogName for the name, and a catalogOptions map for the rest, which the README notes carries all remaining options. This is the piece that matters most in practice: without a catalog, a conversion writes table metadata where you told it to and nothing registers the table with a metastore, so a query engine will not see it. There is an xtable-hive-metastore/ module in the repository, so the Hive Metastore is at least on the table of what this project expects to support.
Style violations break the build, not just the review
The style guide section is short and unusually strict. The project uses the Maven Spotless plugin with Google Java Format, so ./mvnw spotless:check finds violations and ./mvnw spotless:apply fixes them. The part that matters is the last sentence: the code style check is tied to the compile phase by default, so a violation fails the build. That is a good default and a surprising one if you arrive from a project where formatting is a separate CI step, because your first build may fail on formatting rather than on code. It also means running spotless:apply before you push is faster than reading a failed build log. The README additionally points automation and coding agents at AGENTS.md for the repository specific workflow, especially dependency updates, shaded modules and targeted validation.
Against running a conversion job per target format
The alternative is what most teams do: write one ingest pipeline per format, or accept that a table is readable only by the engine that wrote it. That is the status quo XTable is meant to remove, and the cost comparison is the whole argument, because one conversion job per consumer pair multiplies as you add formats and engines. A second alternative is to pick one format and migrate everything once, which is cheaper in the long run when you can actually commit to it, and impossible when another team owns the target. XTable's position is the in between case, and its risk is the one any metadata layer has: it sits between your data and your engines, so a conversion bug produces tables that are wrong in ways the query engine will happily return. The incubating status and the version numbering are the honest signal about how much maturity to assume.
Editorial conclusion
XTable fits a lakehouse team that cannot pick one table format, since the whole point is writing in one format and letting engines that speak another read it, without maintaining two ingest pipelines per consumer. Skip it if you control the whole stack and can standardise on a single format, because conversion jobs are extra operational surface, and note the version cadence, 0.4.0-incubating from 2026-08-24, means an incubating project with a moving API. Before running it, copy the datasets block from the README yaml and get the partitionSpec right for Hudi sources, since the spec is required there and ignored or wrong elsewhere, and read the AGENTS.md the README points at, which documents the repository specific workflow for dependency updates and shaded modules. It is Apache-2.0.
Frequently asked questions
Which table formats does Apache XTable convert between?
It currently supports Apache Hudi, Apache Iceberg and Delta Lake, using a common table representation so data written in one format can be read by engines that understand another. Creating a transformation to a new format requires implementing a few interfaces.
What does the partitionSpec field in the conversion config do?
It lets the converter infer partition values and is a comma separated list of path:type:format entries, where path is a dot separated field path, type is VALUE, YEAR, MONTH, DAY or HOUR, and format is the date string format for the date granularities. The README states it is only required for Hudi source tables, and should be left blank if the table is not partitioned.
How do I build and test Apache XTable?
Use Java 11 and the Maven wrapper: ./mvnw clean package to build, with -DskipTests to skip tests, ./mvnw test for unit tests, a single test with -Dtest=TestDeltaSync -pl xtable-core, and ./mvnw verify for integration tests. With Maven 3.9 or above the build is cached, so add -Dmaven.build.cache.enabled=false to ignore it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/apache-incubator-xtable)Was this useful?