ORA-06564: Why “Does Not Exist” Lies to You

Oracle directory objects, external tables, and the visibility model that breaks between environments.


You run a CREATE TABLE ... ORGANIZATION EXTERNAL statement. It worked yesterday in dev. Today, in the staging environment after a Data Pump import, it throws:

ORA-06564: object EXT_DIR does not exist

You check dba_directories. The directory is there. You re-run the statement. Same error. You start questioning your own sanity — the object exists, Oracle says it does not.

This is ORA-06564 in its natural habitat: an error message that tells you what failed but actively misleads you about why. The word “exist” in this error covers at least five distinct failure modes, and only one of them is actually about the object not being created. The rest are about visibility scope, privilege inheritance, namespace isolation, and container context — problems that surface specifically when you move between environments, schemas, or Oracle versions.

This error appears disproportionately often during cross-environment migrations, Data Pump restores, external table automation, multitenant upgrades, and legacy ETL stabilization — especially in systems where operational scripts assume global directory visibility inherited from pre-12c architectures. If you are maintaining or migrating enterprise Oracle systems, you will hit this error repeatedly, and each time the root cause may be different.

This article traces each failure mode, shows how to diagnose it, and maps the behavioral differences across Oracle versions from 11g through 23ai.

What ORA-06564 Actually Means

Oracle’s official description is deceptively simple:

ORA-06564: object <name> does not exist
Cause: The named object could not be found.
        Either it does not exist or you do not have permission to access it.
Action: Create the object or get permission to access it.

That second sentence — “either it does not exist or you do not have permission” — is doing heavy lifting. Oracle collapses two fundamentally different problems into one error code. There is no way to distinguish “this directory was never created” from “this directory exists but you cannot see it” without running additional queries yourself. This becomes especially painful during incident response, because teams lose time debugging object creation instead of investigating visibility scope and privilege inheritance — two very different diagnostic paths.

The error most commonly appears in three contexts: external table operations (the classic case), Data Pump export/import, and PL/SQL code referencing directory objects through UTL_FILE or DBMS_LOB. But the underlying mechanism is the same: Oracle resolves the directory name, fails to find it in the current session’s visibility scope, and raises ORA-06564.

The Classic Scenario

Here is the setup that produces the error in its simplest form:

CREATE TABLE ext_table (
   id   NUMBER,
   name VARCHAR2(100)
)
ORGANIZATION EXTERNAL (
   TYPE ORACLE_LOADER
   DEFAULT DIRECTORY ext_dir
   ACCESS PARAMETERS (
      RECORDS DELIMITED BY NEWLINE
      FIELDS TERMINATED BY ','
   )
   LOCATION ('data.csv')
)
REJECT LIMIT UNLIMITED;
ORA-06564: object EXT_DIR does not exist

The fix appears obvious: create the directory. And sometimes it really is that simple. But in production environments — where objects are migrated, schemas are shared, and databases are upgraded — the obvious answer is usually the wrong one.

Why This Error Appears So Often in Enterprise Environments

In isolated development environments, ORA-06564 is trivial — you forgot to create a directory, you create it, you move on. In enterprise systems, the error is a symptom of deeper operational problems that compound across environments and Oracle versions.

Legacy operational scripts assume global directory visibility — a deployment assumption that held true in 11g but silently breaks after the transition to multitenant architecture. Migrations recreate schemas, tables, indexes, and constraints, but directory objects fall outside the Data Pump export scope, creating environment parity gaps that are invisible until runtime. Privilege management is often handled by a separate DBA team, disconnected from the application team that defines external tables — so the directory exists, but the grants are missing. PDB namespace isolation breaks old automation that worked for years against a single-instance database. And database clones for DR validation or performance testing inherit table definitions but not the infrastructure boundaries they depend on.

The result is operational drift: the same deployment artifact behaves differently across environments not because of code changes, but because of invisible differences in directory visibility, privilege state, and container context.

Typical Production Scenarios

ORA-06564 most commonly surfaces in these operational contexts:

  • ETL jobs after a database clone — the clone replicates external table definitions but not the directory objects or their OS-level paths on the target server.
  • Data Pump migration between versions — especially 11g to 19c, where the shift from single-instance to PDB-local directories catches teams off guard.
  • Cross-schema batch processing — jobs running under a service account that has SELECT on the table but not READ on the directory, with the error appearing only at query time.
  • DR restore validation — a standby or restored database passes all schema checks, but the first external table query fails because directory objects were never part of the DR checklist.
  • External table automation in CI/CD — pipeline scripts that create tables dynamically but assume directories were pre-provisioned by infrastructure, leading to failures when the deployment target changes.
  • Post-upgrade PDB provisioning — after upgrading to 21c or 23ai where non-CDB is desupported, existing scripts that relied on global directory scope fail in the new mandatory CDB architecture.

Root Cause 1: The Directory Was Never Created

The most straightforward case. The CREATE DIRECTORY statement was never executed, or it was executed in a different database, a different PDB, or under a different assumption about who owns the setup.

Diagnosis:

-- As a DBA:
SELECT owner, directory_name, directory_path
FROM dba_directories
WHERE directory_name = 'EXT_DIR';

-- As a regular user (shows only directories you have privileges on):
SELECT directory_name, directory_path
FROM all_directories
WHERE directory_name = 'EXT_DIR';

If dba_directories returns nothing — the directory genuinely does not exist. Create it:

CREATE OR REPLACE DIRECTORY ext_dir AS '/data/external';
GRANT READ, WRITE ON DIRECTORY ext_dir TO app_user;

One thing to note: CREATE DIRECTORY does not validate that the OS path actually exists. Oracle will happily create a directory object pointing to /nonexistent/path. You will not get ORA-06564 for this — instead, you will get ORA-29913 or ORA-29400 when you actually try to read data. These are different errors with different root causes, and mixing them up wastes debugging time.

Root Cause 2: Case Sensitivity

Oracle stores unquoted identifiers in uppercase. This is well-known for table and column names, but engineers routinely forget it applies to directory objects too.

-- This creates a directory named EXT_DIR (uppercase):
CREATE DIRECTORY ext_dir AS '/data/external';

-- This creates a directory named Ext_Dir (mixed case):
CREATE DIRECTORY "Ext_Dir" AS '/data/external';

If someone created the directory with double quotes, every subsequent reference must also use double quotes with the exact same case. Without them, Oracle looks for EXT_DIR, does not find Ext_Dir, and raises ORA-06564.

Diagnosis:

SELECT directory_name FROM all_directories;

Look at the exact value. If it is not all-uppercase, someone used double quotes at creation time. The fix is either to recreate the directory without quotes, or to reference it with the correct case everywhere:

-- If the directory was created as "Ext_Dir":
CREATE TABLE ext_table (...)
ORGANIZATION EXTERNAL (
   TYPE ORACLE_LOADER
   DEFAULT DIRECTORY "Ext_Dir"
   ...
);

The practical rule: never use double quotes in CREATE DIRECTORY unless you have a specific reason and are prepared to enforce that casing across all references — DDL, PL/SQL, Data Pump parameters, and application code.

Root Cause 3: Permissions Masquerading as Non-Existence

This is where ORA-06564 actively lies. The directory exists. It is visible in dba_directories. But the current user does not have READ or WRITE privileges on it — and Oracle reports this as “does not exist” rather than “insufficient privileges.”

This is by design: Oracle treats directory object visibility as a security boundary. If you do not have access, you do not get to know whether the object exists. But for the engineer debugging a failed ETL pipeline at midnight, this design choice is indistinguishable from a bug — and it makes incident response slower because the error message sends you down the wrong diagnostic path.

The most common sub-scenario: cross-schema access to external tables.

-- Schema OWNER_SCHEMA creates the external table:
CREATE TABLE owner_schema.ext_data (...)
ORGANIZATION EXTERNAL (
   TYPE ORACLE_LOADER
   DEFAULT DIRECTORY data_dir
   ...
);

-- Schema APP_SCHEMA tries to query it:
SELECT * FROM owner_schema.ext_data;
-- ORA-06564: object DATA_DIR does not exist

Granting SELECT on the table is not enough. The querying user also needs READ on the directory:

GRANT READ ON DIRECTORY data_dir TO app_schema;

A related trap: ALTER SESSION SET CURRENT_SCHEMA = OWNER_SCHEMA. This changes the default schema for name resolution, but it does not inherit the other schema’s directory privileges. You will still get ORA-06564 on the directory, even though the table resolves correctly.

Diagnosis — check what privileges your current user actually has:

SELECT grantee, table_name AS directory_name, privilege
FROM all_tab_privs
WHERE table_name = 'DATA_DIR'
  AND privilege IN ('READ', 'WRITE');

If your user is not listed — that is the problem. The directory exists; you just cannot see it.

Root Cause 4: Data Pump Does Not Export Directory Objects

This is the root cause that burns teams during migrations. expdp does not include directory object definitions in the dump file. It exports the table metadata (including the directory name reference), but not the directory itself.

After importing with impdp, the external table definition references a directory name that does not exist in the target database. The import itself may succeed — Oracle creates the table metadata — but any SELECT from the table immediately fails with ORA-06564.

Worse: if the import is a full schema import with hundreds of objects, this failure is easy to miss until someone actually queries the external table days later.

-- On source (works fine):
SELECT * FROM ext_data;  -- OK

-- After expdp/impdp to target:
SELECT * FROM ext_data;
-- ORA-06564: object DATA_DIR does not exist

During Data Pump import, you may also see ORA-06564 wrapped inside ORA-39083:

ORA-39083: Object type TABLE:"SCHEMA"."EXT_TABLE" failed to create with error:
ORA-06564: object DATA_DIR does not exist

This means the import itself failed to create the table because the directory was missing.

The fix is procedural, not technical: directory objects must be an explicit step in your migration runbook, not an assumption about environment parity.

Before import:

  1. Query dba_directories on the source database
  2. Create matching directories on the target with appropriate OS paths (which may differ between servers)
  3. Grant the same privileges
  4. Only then run impdp
-- On source — document what you have:
SELECT directory_name, directory_path FROM dba_directories
WHERE directory_name IN (
   SELECT directory_name FROM dba_external_tables
);

-- On target — recreate with correct paths:
CREATE OR REPLACE DIRECTORY data_dir AS '/target/data/external';
GRANT READ, WRITE ON DIRECTORY data_dir TO app_user;

Root Cause 5: Multitenant Container Context (12c and Later)

Starting with Oracle 12c, directory objects are PDB-local. A directory created in CDB$ROOT is not visible from a pluggable database. A directory created in PDB1 is not visible from PDB2.

This is a fundamental architectural change from 11g, where the database was a single container and all directories were globally visible. If you are migrating from 11g to 12c or later — or stabilizing a system that was recently upgraded — this is the root cause that will most likely surprise you, because it invalidates deployment assumptions that may have been embedded in operational scripts for years.

-- Connected to CDB$ROOT:
CREATE DIRECTORY shared_dir AS '/data/shared';

-- Switch to PDB:
ALTER SESSION SET CONTAINER = pdb_app;
SELECT * FROM all_directories WHERE directory_name = 'SHARED_DIR';
-- No rows returned

-- Any reference to SHARED_DIR here raises ORA-06564

Starting with 12.2, Oracle automatically creates a unique DATA_PUMP_DIR for each PDB. This was a direct response to the confusion caused by PDB-level directory isolation: in 12.1, it was common for administrators to create DATA_PUMP_DIR in the root and then wonder why Data Pump failed from within a PDB.

Diagnosis — always verify which container you are connected to:

SHOW CON_NAME;
-- or
SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM dual;

Then check directories within that container:

SELECT directory_name, directory_path FROM all_directories;

If you are working in a multitenant environment and need a directory available across PDBs, you have two options: create it in each PDB individually (more isolation, more maintenance, higher operational drift risk), or use an application container where objects can be shared across member PDBs (less duplication, but more architectural complexity and tighter coupling). Neither option is free — this is a trade-off between operational simplicity and infrastructure isolation that needs to be made explicitly, not discovered during an incident.

For a deeper treatment of PDB-level directory isolation, PATH_PREFIX restrictions, and lockdown profiles — these deserve a separate article. For ORA-06564 diagnosis, the key takeaway is: check your container context first.

Version-Specific Behavior Map

The way Oracle handles directory objects and external tables has evolved significantly across major versions. Here is what changed and when:

Oracle 11g (11.1, 11.2)

The baseline. No multitenant architecture — a single database instance with globally visible directory objects. External tables use ORACLE_LOADER or ORACLE_DATAPUMP access drivers. ORA-06564 in 11g almost always means the directory was not created or you lack privileges. There is no container context to worry about.

One 11g-specific behavior worth noting: ORACLE_DATAPUMP external tables have stricter validation. If you reference a directory that does not exist in a CREATE TABLE ... ORGANIZATION EXTERNAL (TYPE ORACLE_DATAPUMP ...), the error is immediate. With ORACLE_LOADER, the error may be deferred until you actually query the table, depending on the operation.

Oracle 12c Release 1 (12.1)

Multitenant architecture introduced. Directory objects become PDB-local. This is the version where teams migrating from 11g first encounter “the directory exists in the root but not in my PDB” problem.

PATH_PREFIX was introduced as a PDB property at creation time to restrict directory object paths to a specific subtree. In 12.1, the implementation had known limitations — absolute paths could sometimes bypass the restriction. Still, if PATH_PREFIX is set and your directory points outside the allowed path, CREATE DIRECTORY itself will fail, and subsequent references will hit ORA-06564.

Oracle 12c Release 2 (12.2)

Two significant additions:

EXTERNAL MODIFY clause. You can now override external table parameters (directory, location, reject limit) directly in a SELECT statement without issuing ALTER TABLE:

SELECT * FROM ext_table
EXTERNAL MODIFY (
   DEFAULT DIRECTORY other_dir
   LOCATION ('other_file.csv')
);

If other_dir does not exist or you lack privileges — ORA-06564 at query time, not at DDL time. This moves the failure point from table creation to data access, which can be confusing in application code that does not expect a SELECT to throw a directory-related error.

Important limitation: the DEFAULT DIRECTORY in EXTERNAL MODIFY does not accept bind variables. If you try to parameterize the directory name in PL/SQL, you will get ORA-06564 with the variable name as the “object” — not the directory name.

PDB lockdown profiles introduced, allowing CDB administrators to restrict directory-related operations at the PDB level. DATA_PUMP_DIR automatically created per PDB with unique paths.

Oracle 18c

Inline external tables. You can define an external table directly in the FROM clause without a persistent CREATE TABLE:

SELECT * FROM EXTERNAL (
   (id NUMBER, name VARCHAR2(100))
   TYPE ORACLE_LOADER
   DEFAULT DIRECTORY ext_dir
   ACCESS PARAMETERS (
      RECORDS DELIMITED BY NEWLINE
      FIELDS TERMINATED BY ','
   )
   LOCATION ('data.csv')
   REJECT LIMIT UNLIMITED
);

The directory object still must exist and be accessible. ORA-06564 behavior is identical, but now it appears in an ad-hoc query context where there is no table to inspect for directory references.

Oracle 19c

The long-term support release and the primary migration target for enterprises leaving 11g. No fundamental changes to directory object behavior, but this is the version where ORA-06564 multitenant issues are most frequently encountered in practice — simply because this is where the bulk of production stabilization work happens after a major version upgrade.

DBMS_CLOUD package (primarily for Autonomous Database) introduces a different model for external data access that sidesteps traditional directory objects entirely, using credentials and URIs instead. On-premises 19c installations still rely on directory objects.

Oracle 21c

The non-CDB architecture is officially desupported. Every database is now a CDB, even single-tenant configurations. This is a hard infrastructure boundary: PDB-local directory behavior is no longer optional, it is the only mode.

If you maintained 11g-era scripts that assumed global directory visibility, they will fail in 21c even in a single-PDB configuration, because the PDB is still a container with its own namespace. This is where years of accumulated deployment assumptions about directory objects finally break — not because something changed in the scripts, but because the platform no longer supports the model those scripts were written for.

Oracle 23ai

Further refinements to external data access, including expanded cloud integration. The directory object model remains unchanged for on-premises deployments. The core ORA-06564 behavior — including the permission-masquerading-as-non-existence issue — has not been addressed in the error message itself across any version.

Diagnostic Checklist

When you hit ORA-06564 in production, work through these steps in order. Each step eliminates one root cause and narrows the visibility scope. The sequence is deliberate — container context and permissions are more common root causes in enterprise environments than actual missing objects.

Step 1. Verify the object exists.

SELECT directory_name, directory_path
FROM dba_directories
WHERE directory_name = UPPER('ext_dir');

No rows → the directory was never created, or you are in the wrong container (step 5).

Step 2. Check for case sensitivity.

SELECT directory_name FROM dba_directories
WHERE UPPER(directory_name) = 'EXT_DIR';

If the result is not exactly EXT_DIR in uppercase — someone created it with double quotes. Either recreate without quotes or match the exact case in all references.

Step 3. Verify your privileges.

SELECT grantee, table_name, privilege
FROM dba_tab_privs
WHERE table_name = 'EXT_DIR'
  AND privilege IN ('READ', 'WRITE');

Your user (or a role granted to your user) must appear here. If not — GRANT READ, WRITE ON DIRECTORY ext_dir TO your_user.

Step 4. Check cross-schema context.

If you are accessing an external table in another schema, you need both SELECT on the table and READ on the directory. ALTER SESSION SET CURRENT_SCHEMA does not inherit directory privileges.

Step 5. Verify container context (12c+).

SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM dual;

Are you connected to the correct PDB? Directory objects are PDB-local. If you need the directory in this PDB, create it here.

Step 6. Verify the OS path exists (eliminates a different error).

-- On the database server:
ls -la /data/external/

If the OS path does not exist, the error will be ORA-29913/ORA-29400, not ORA-06564. But if you are chasing the wrong error, this check saves time.

Operational Anti-Patterns

Granting to PUBLIC as a quick fix. GRANT READ, WRITE ON DIRECTORY ext_dir TO PUBLIC resolves the permissions issue instantly but opens a security hole — every user in the database can now read and write files through that directory. In environments with audit requirements, compliance constraints, or multi-tenant isolation, this is unacceptable and will be flagged during security review. Grant to specific users or roles.

Using CREATE OR REPLACE DIRECTORY without checking. This statement silently overwrites the existing path. If another schema, application, or ETL pipeline depends on the current path, you have just broken their external tables without any warning or error. In shared environments, always query dba_directories before replacing — and coordinate with other teams that may depend on the same directory object.

Not including directories in migration runbooks. Data Pump does not export directory objects. If your migration runbook does not include a step for directory recreation — with target-appropriate OS paths and privilege grants — you will hit ORA-06564 after every import. This is a classic case of operational drift between environments: the schema is identical, but the infrastructure boundary it depends on is missing. Automate this: query dba_directories and dba_external_tables on the source, generate the CREATE DIRECTORY and GRANT statements, and include them in the pre-import script.

Ignoring the difference between all_directories and dba_directories. A user querying all_directories and seeing no rows may conclude the directory does not exist. But all_directories only shows directories the user has privileges on. The directory may exist — dba_directories would show it — but the user simply cannot see it. This distinction is critical during debugging: always check from a DBA session first.

Key Takeaways

ORA-06564 is rarely about a missing object.

In modern Oracle environments, it is almost always a symptom of visibility boundaries: privilege boundaries that masquerade as non-existence, container isolation that breaks legacy assumptions, migration drift where schemas arrive but their infrastructure dependencies do not, or operational scripts built for a pre-12c world where directory objects were globally visible.

The fastest way to debug it is to stop asking “does the object exist?” and start asking: “from which scope is Oracle trying to resolve it?”

That shift — from existence checking to visibility scoping — is what separates a five-minute fix from a two-hour incident. Container context first, then privileges, then naming, then actual existence. In that order.

Three things to lock into your migration and deployment process:

  • Directory objects are not covered by Data Pump. They must be an explicit step in every migration runbook, not an implicit assumption about environment parity.
  • In multitenant architectures (mandatory since 21c), directory visibility is PDB-local. Global visibility no longer exists. Scripts that assume it will fail silently until something queries an external table.
  • The error message has not changed across any Oracle version from 11g through 23ai. The architecture around it has changed fundamentally. The same error, different root causes — and each one requires a different diagnostic path.