apache/cassandra · error · RuntimeException
Can't open incompatible SSTable! Current version
Error message
Can't open incompatible SSTable! Current version %s, found file: %s
What it means
moveAndOpenSSTable() refuses to move/open an SSTable whose descriptor version is not compatible with the currently supported format. It throws a RuntimeException naming the latest supported version and the offending file, preventing opening of SSTables written by incompatible Cassandra versions or formats.
Solutions
- Run nodetool upgradesstables (or sstableupgrade) on the data before moving/opening
- Load old SSTables through sstableloader, which rewrites them to the current version
- If the file truly is incompatible/unreadable, remove it and rebuild data via nodetool repair from other replicas
- Verify the intended version with sstablemetadata and confirm the Cassandra version matches the data files
Example fix
// before: moving and opening old-version sstable directly SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false); // after: upgrade first // run: nodetool upgradesstables --ks ks --cf tbl SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false);
Defensive patterns
Strategy: validation
Validate before calling
// check descriptor compatibility before moving
Descriptor d = Descriptor.fromFilename(file);
if (!d.isCompatible()) { upgradeFirst(); } // e.g. run sstableupgrade Try / catch
try { SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false); }
catch (RuntimeException e) {
if (e.getMessage().startsWith("Can't open incompatible SSTable")) {
logger.error("SSTable version incompatible: {}", oldDesc, e);
runUpgradesstables();
} else throw e;
} Prevention
- Always run nodetool upgradesstables after major-version upgrades before tooling
- Check sstable version with sstablemetadata before offline operations
- Keep backup/restore tooling version-matched to the cluster
- Follow the documented upgrade path between Cassandra versions
When it happens
Trigger: Calling moveAndOpenSSTable (e.g. during sstable upgrade/relocation or offline tools) with oldDescriptor whose version fails isCompatible() — typically SSTables from an older major version no longer supported, or foreign/exotic formats.
Common situations: Upgrading from a very old Cassandra release without running upgradesstables first; restoring backups written by an older major version; point tools at mixed-version data directories.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Invalid sstable file
- Prepare phase failed because it encountered legacy sstables…
- is not the latest version, run upgradesstables
- is of version which is now unsupported and cannot be read.
- 3
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/411bc9fe13791764.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java:1869
@Override
public final boolean equals(Object o)
{
if (!(o instanceof IndexesBounds))
return false;
IndexesBounds that = (IndexesBounds) o;
return lowerPosition == that.lowerPosition && upperPosition == that.upperPosition;
}
}
/**
* Moves the sstable in oldDescriptor to a new place (with generation etc) in newDescriptor.
* <p>
* All components given will be moved/renamed
*/
public static SSTableReader moveAndOpenSSTable(ColumnFamilyStore cfs, Descriptor oldDescriptor, Descriptor newDescriptor, Set<Component> components, boolean copyData)
{
if (!oldDescriptor.isCompatible())
throw new RuntimeException(String.format("Can't open incompatible SSTable! Current version %s, found file: %s",
oldDescriptor.getFormat().getLatestVersion(),
oldDescriptor));
boolean isLive = cfs.getLiveSSTables().stream().anyMatch(r -> r.descriptor.equals(newDescriptor)
|| r.descriptor.equals(oldDescriptor));
if (isLive)
{
String message = String.format("Can't move and open a file that is already in use in the table %s -> %s", oldDescriptor, newDescriptor);
logger.error(message);
throw new RuntimeException(message);
}
if (newDescriptor.fileFor(Components.DATA).exists())
{
String msg = String.format("File %s already exists, can't move the file there", newDescriptor.fileFor(Components.DATA));
logger.error(msg);
throw new RuntimeException(msg);
}
View on GitHub (pinned to 88fd0f6a0e)