alibaba/spring-ai-alibaba · critical · RuntimeException

Unable to create tables

Error message

Unable to create tables

What it means

OracleSaver.initTables wraps any SQLException raised while batch-executing the DDL statements that create the checkpoint tables and index into a RuntimeException('Unable to create tables'). This happens during saver initialization when Oracle rejects the CREATE TABLE / CREATE INDEX statements. It means the checkpoint persistence schema could not be set up, so the saver cannot be used.

Solutions

  1. Grant the connecting Oracle user CREATE TABLE privilege (GRANT CREATE TABLE TO user) or run initTables as a DBA-backed schema user.
  2. Check the wrapped SQLException cause for the exact ORA- code; if ORA-00955 (name in use), the tables already exist and you can skip initTables.
  3. Verify the JDBC URL, service name, and credentials; test connectivity with a simple SELECT 1 FROM DUAL before initializing.
  4. If initialization is idempotency-related, pre-create the checkpoint tables/index manually with the exact names the saver expects.

Example fix

// before
OracleSaver.builder().dataSource(ds).stateSerializer(serializer).build(); // fails: no CREATE TABLE privilege
// after
// grant DDL rights or pre-create tables, then optionally skip auto-init:
// GRANT CREATE TABLE TO app_user;
OracleSaver.builder().dataSource(ds).stateSerializer(serializer).build();
Defensive patterns

Strategy: try-catch

Validate before calling

// Java: pre-check privileges and existing tables before initTables
try (Connection c = dataSource.getConnection(); Statement s = c.createStatement()) {
    s.execute("SELECT 1 FROM DUAL"); // connectivity
    ResultSet rs = c.getMetaData().getTables(null, null, "CHECKPOINTS", new String[]{"TABLE"});
    if (rs.next()) { /* tables already exist: skip init */ }
}

Try / catch

try {
    saverBuilder.build();
} catch (RuntimeException e) {
    if ("Unable to create tables".equals(e.getMessage()) && e.getCause() instanceof SQLException sql) {
        // inspect sql.getErrorCode() for ORA-00955 / ORA-01031 and act accordingly
    }
}

Prevention

When it happens

Trigger: Calling OracleSaver builder initTables (or constructing a saver that auto-initializes) when the DB user lacks CREATE TABLE privilege, a table with the same name already exists with an incompatible definition, the connection points to an unreachable/invalid schema, or Oracle throws ORA- errors on the DDL batch (e.g. ORA-00955 name already used, ORA-01031 insufficient privileges).

Common situations: Deploying to an Oracle account with read/write but no DDL permissions; pointing at a shared schema where the checkpoint tables already exist; wrong JDBC URL or service name; network/credentials problems surfacing as SQL exceptions during DDL.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/83d27328ac26811a. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/oracle/OracleSaver.java:579

	 */
	protected void initTables() {
		try (Connection connection = dataSource.getConnection();
			 Statement statement = connection.createStatement()) {
			if (createOption == CreateOption.CREATE_OR_REPLACE) {
				statement.addBatch(DROP_THREAD_INDEX);
				statement.addBatch(DROP_CHECKPOINT_TABLE);
				statement.addBatch(DROP_THREAD_TABLE);
			}
			if (createOption == CreateOption.CREATE_OR_REPLACE ||
					createOption == CreateOption.CREATE_IF_NOT_EXISTS) {
				statement.addBatch(CREATE_THREAD_TABLE);
				statement.addBatch(INDEX_THREAD_TABLE);
				statement.addBatch(CREATE_CHECKPOINT_TABLE);
				statement.executeBatch();
			}
		}
		catch (SQLException sqlException) {
			throw new RuntimeException("Unable to create tables", sqlException);
		}
	}

	/**
	 * A builder for OracleSaver.
	 */
	public static class Builder {
		private DataSource dataSource;
		private CreateOption createOption = CreateOption.CREATE_IF_NOT_EXISTS;
		private StateSerializer stateSerializer;
		private int maxCachedThreads = 1024;

		/**
		 * Sets the datasource
		 *
		 * @param dataSource the datasource
		 * @return this builder
		 */

View on GitHub (pinned to f82da0b50f)