ORA-06550 — A Missing Semicolon Took 47 Objects Offline
ORA-06550: CREATE OR REPLACE silently compiles invalid packages.
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- ORA-06550 is a compile-time error — the PL/SQL block has syntax or semantic issues and will not execute until they are fixed
- It always includes a line number, column number, and a PLS error code that identifies the exact failure category
- CREATE OR REPLACE does not raise an exception on compilation failure — it silently marks the object INVALID
- A single invalid object cascades through the dependency tree — all callers are marked INVALID and raise ORA-04063
- PLS-00904 (insufficient privilege) looks like a code bug but is always a missing GRANT — check privileges before debugging code
ORA-06550 is PL/SQL's generic compilation error, triggered when the compiler encounters a syntax or semantic issue in a PL/SQL block, function, procedure, package, or trigger. It's not the root cause itself—it's the carrier that wraps a more specific PLS-xxxxx error code (like PLS-00103 for a missing semicolon) along with the exact line and column number.
The error exists because Oracle's PL/SQL engine validates code at compile time, not runtime, and it surfaces when you execute a CREATE OR REPLACE, anonymous block, or call to a stored procedure that fails to parse. In production, this error is infamous for cascading: a single missing semicolon in a package body can invalidate not just that package, but every dependent object—views, other procedures, even application code—via ORA-04063, taking dozens of objects offline in seconds.
You'll see it most often during deployment scripts where a CREATE OR REPLACE silently fails, leaving the old version intact but invalidating everything that references it. The key distinction is between syntax errors (like missing semicolons, which PLS-00103 catches) and semantic errors (like referencing a nonexistent column, which PLS-00201 catches).
For privilege issues, PLS-00904 specifically flags insufficient object privileges at compile time. In practice, ORA-06550 is your first clue that a deployment went sideways—check the PLS code, fix the line, and recompile before your monitoring alerts wake you at 3 AM.
ORA-06550 is Oracle telling you: I cannot compile this code. It is a compile-time error, meaning the code never executes — Oracle rejects it before any rows are read or written. Unlike ORA-01403 or ORA-01422 which happen during execution, ORA-06550 stops everything at deployment time. The line and column numbers in the error message point directly to the problem. The danger is that CREATE OR REPLACE reports success even when compilation fails — the object sits in the database marked INVALID, and every procedure that calls it also becomes invalid.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
ORA-06550: line %s, column %s is a PL/SQL compilation error. It occurs when the Oracle compiler encounters a syntax error, a semantic error, or a privilege issue while compiling a PL/SQL block, package, procedure, function, or trigger. The error includes line and column numbers that point to the exact location of the failure, followed by a PLS error code that identifies the specific failure category.
Unlike runtime errors that occur during execution, ORA-06550 prevents execution entirely. The compiled object is marked INVALID in the data dictionary. Any object that depends on the invalid object — callers, triggers, views referencing the function — is also marked INVALID and will raise ORA-04063 or ORA-06508 when invoked.
The cascading effect is the production danger. A single ORA-06550 in a core package body can invalidate dozens of dependent procedures, triggers, and views across multiple schemas. The error appears at deployment time, but its impact propagates through the entire dependency tree. Engineers who see ORA-04063 in production logs do not immediately connect it to the ORA-06550 that caused the cascade — they see the dependent failure, not the root cause.
This guide covers the full lifecycle of ORA-06550: reading PLS error codes, distinguishing syntax from semantic errors, understanding cascading invalidation, diagnosing privilege failures, safe deployment patterns, batch recompilation with UTL_RECOMP, and CI/CD validation. All examples are runnable on Oracle 19c, 21c, and 23ai.
Why a Missing Semicolon Can Cascade Into a Production Outage
ORA-06550 is PL/SQL's compilation error signal. It fires when the PL/SQL engine encounters a syntax or semantic error during anonymous block, function, procedure, or package compilation. The error message itself is generic — it always includes a line number and a secondary error (e.g., PLS-00103) that pinpoints the actual violation. The core mechanic: PL/SQL compiles all code before execution, so any syntax error — even a single missing semicolon — blocks the entire unit from being created or executed.
In practice, ORA-06550 surfaces during deployment scripts, batch jobs, or dynamic SQL execution. The error is immediate and fatal: the entire block fails, not just the offending line. This is critical because a single bad statement in a large deployment script can prevent all subsequent objects (procedures, functions, triggers) from compiling. The error's line number is relative to the anonymous block, not the file, making debugging harder without proper logging.
You encounter ORA-06550 whenever you run PL/SQL code that fails to compile — during schema migrations, stored procedure updates, or ad-hoc queries. It matters because in production, a deployment script with a typo can take 47 dependent objects offline simultaneously. The fix is always local (fix the syntax), but the blast radius can be global. Treat every PL/SQL compilation error as a potential deployment blocker.
ORA-06550 Anatomy: Line, Column, and PLS Error Code
ORA-06550 always includes three components: the line number, the column number, and a PLS error code that identifies the specific failure category. The line and column reference the position within the PL/SQL block being compiled — not the position in your source file or deployment script.
The PLS error code that follows ORA-06550 is the diagnostic that matters. ORA-06550 tells you where. The PLS code tells you what. Common PLS codes:
PLS-00103 (Encountered symbol X when expecting Y) is the most common syntax error. The compiler found an unexpected token. The error message shows both what was found and what was expected, which usually makes the fix obvious.
PLS-00201 (identifier X must be declared) means a referenced identifier — variable, cursor, type, table, package — does not exist in the current compilation scope. Causes: typo in the name, missing declaration, missing synonym, missing GRANT, or referencing an object that has not been created yet.
PLS-00302 (component X must be declared) means a field or method does not exist on a referenced type, record, or package. Causes: the field was renamed, removed, or never existed in the type definition.
PLS-00306 (wrong number or types of arguments) means the call to a procedure or function has the wrong parameter count or incompatible parameter types. Causes: a parameter was added or removed from the called procedure, or a data type mismatch.
PLS-00904 (insufficient privilege to access object) means the compiling schema lacks EXECUTE or SELECT privilege on a referenced object. The object exists and the name is correct — the privilege is missing. This is the error that wastes the most debugging time because engineers look at the code instead of the grants.
Oracle reports one error per compilation attempt. Fix the first error, recompile, and the next error appears. Do not assume a single fix resolves all errors — repeat until USER_ERRORS returns zero rows for the object.
-- ============================================================ -- Setup: create a table and helper package for the examples -- Runnable on Oracle 19c, 21c, 23ai -- ============================================================ CREATE TABLE demo_accounts ( account_id NUMBER PRIMARY KEY, account_type VARCHAR2(30), balance NUMBER(15,2) ); INSERT INTO demo_accounts VALUES (1, 'CHECKING', 5000); COMMIT; CREATE OR REPLACE PACKAGE demo_util_pkg AS FUNCTION calculate_tax(p_amount IN NUMBER, p_region IN VARCHAR2) RETURN NUMBER; END demo_util_pkg; / CREATE OR REPLACE PACKAGE BODY demo_util_pkg AS FUNCTION calculate_tax(p_amount IN NUMBER, p_region IN VARCHAR2) RETURN NUMBER IS BEGIN RETURN p_amount * 0.08; END calculate_tax; END demo_util_pkg; / -- ============================================================ -- Example 1: PLS-00103 — Missing semicolon after variable declaration -- ============================================================ CREATE OR REPLACE PROCEDURE demo_syntax_error IS v_count NUMBER -- Missing semicolon here BEGIN SELECT COUNT(*) INTO v_count FROM demo_accounts; END demo_syntax_error; / -- Error output: -- ORA-06550: line 3, column 1: -- PLS-00103: Encountered the symbol "BEGIN" when expecting one of the following: -- := . ( @ % ; not null range default character -- Fix: add semicolon after NUMBER on line 2 SHOW ERRORS PROCEDURE DEMO_SYNTAX_ERROR -- ============================================================ -- Example 2: PLS-00201 — Undeclared variable -- ============================================================ CREATE OR REPLACE PROCEDURE demo_undeclared_var IS BEGIN v_total := 100; -- v_total was never declared DBMS_OUTPUT.PUT_LINE(v_total); END demo_undeclared_var; / -- Error output: -- ORA-06550: line 3, column 3: -- PLS-00201: identifier 'V_TOTAL' must be declared -- Fix: add DECLARE section with v_total NUMBER; -- ============================================================ -- Example 3: PLS-00306 — Wrong number of arguments -- ============================================================ CREATE OR REPLACE PROCEDURE demo_wrong_args IS v_result NUMBER; BEGIN -- calculate_tax expects 2 args (amount, region) — we passed 3 v_result := demo_util_pkg.calculate_tax(100, 'CA', 'EXTRA'); END demo_wrong_args; / -- Error output: -- ORA-06550: line 5, column 16: -- PLS-00306: wrong number or types of arguments in call to 'CALCULATE_TAX' -- Fix: remove the extra argument -- ============================================================ -- Example 4: PLS-00302 — Component not declared on record type -- ============================================================ CREATE OR REPLACE PROCEDURE demo_missing_component IS v_rec demo_accounts%ROWTYPE; BEGIN SELECT * INTO v_rec FROM demo_accounts WHERE account_id = 1; DBMS_OUTPUT.PUT_LINE(v_rec.nonexistent_field); -- Field does not exist END demo_missing_component; / -- Error output: -- ORA-06550: line 5, column 32: -- PLS-00302: component 'NONEXISTENT_FIELD' must be declared -- Fix: use a valid column name from demo_accounts (e.g., v_rec.balance) -- ============================================================ -- Retrieving all compilation errors for a specific object -- ============================================================ SELECT name, type, line, position, text AS error_text FROM user_errors WHERE name = 'DEMO_SYNTAX_ERROR' ORDER BY sequence; -- Cleanup -- DROP PROCEDURE demo_syntax_error; -- DROP PROCEDURE demo_undeclared_var; -- DROP PROCEDURE demo_wrong_args; -- DROP PROCEDURE demo_missing_component; -- DROP PACKAGE demo_util_pkg; -- DROP TABLE demo_accounts PURGE;
- PLS-00103: Syntax error — the compiler found an unexpected token. The message shows what it found and what it expected. Fix the grammar.
- PLS-00201: Identifier not declared — a variable, table, package, or type does not exist in scope. Check spelling, then check the database for the object.
- PLS-00302: Component not declared — a field or method does not exist on the referenced type or package. Check the type definition.
- PLS-00306: Wrong arguments — the parameter count or types do not match the called procedure. Check the procedure specification.
- PLS-00904: Insufficient privilege — the object exists but the compiling schema lacks EXECUTE or SELECT privilege. Fix with a GRANT, not a code change.
- Oracle reports errors one at a time per compilation — fix the first, recompile, check for the next.
Syntax Errors vs Semantic Errors
ORA-06550 errors fall into two categories that require fundamentally different fix strategies.
Syntax errors mean the code does not follow PL/SQL grammar rules. The compiler cannot parse the text into a valid program structure. Missing semicolons, unmatched parentheses, invalid keywords, and malformed expressions are all syntax errors. PLS-00103 is always a syntax error. The fix is always a code change — correct the grammar at the reported line and column.
Semantic errors mean the code is grammatically correct but references something that does not exist or is used incorrectly in context. The compiler can parse the code but cannot resolve identifiers, types, or privileges. PLS-00201, PLS-00302, PLS-00306, and PLS-00904 are all semantic errors.
The critical difference for debugging: syntax errors are always in the code. Semantic errors may be in the code (typo, wrong argument count) or outside the code (missing GRANT, dropped table, renamed package). When you see a semantic error on code that looks correct, stop debugging the code and start investigating the database state — the object, the synonym, the grant.
-- ============================================================ -- SYNTAX ERRORS — the code violates PL/SQL grammar -- Fix: correct the code structure -- ============================================================ -- Missing semicolon — PLS-00103 CREATE OR REPLACE PROCEDURE demo_missing_semicolon IS v_count NUMBER -- Semicolon missing here BEGIN NULL; END demo_missing_semicolon; / -- Fix: add ; after NUMBER -- Unmatched parenthesis — PLS-00103 CREATE OR REPLACE PROCEDURE demo_unmatched_paren IS v_result NUMBER; BEGIN v_result := demo_util_pkg.calculate_tax(100, 'CA'; -- Missing closing ) END demo_unmatched_paren; / -- Fix: add closing ) before ; -- ============================================================ -- SEMANTIC ERRORS — code is grammatically correct but references -- something that does not exist or cannot be accessed -- Fix: may be code, database objects, or grants -- ============================================================ -- Undeclared variable — PLS-00201 — fix is in the code CREATE OR REPLACE PROCEDURE demo_undeclared IS BEGIN v_total := 100; -- v_total not declared END demo_undeclared; / -- Fix: add v_total NUMBER; in the DECLARE section -- Missing table — PLS-00201 — fix is a database object or synonym CREATE OR REPLACE PROCEDURE demo_missing_table IS v_name VARCHAR2(100); BEGIN SELECT name INTO v_name FROM nonexistent_table_xyz WHERE id = 1; END demo_missing_table; / -- Fix: create the table, create a synonym to the correct table, -- or correct the table name -- Missing privilege — PLS-00904 — fix is a GRANT CREATE OR REPLACE PROCEDURE demo_privilege_error IS BEGIN other_schema.restricted_pkg.do_something; END demo_privilege_error; / -- Fix: GRANT EXECUTE ON other_schema.restricted_pkg TO current_schema; -- The code is correct — the privilege is missing -- ============================================================ -- Diagnostic: determine if the referenced object exists -- Run this when PLS-00201 or PLS-00904 appears -- ============================================================ -- Check if the object exists anywhere in the database SELECT owner, object_name, object_type, status FROM all_objects WHERE object_name = 'RESTRICTED_PKG' ORDER BY owner; -- Check if a synonym exists for the object SELECT owner, synonym_name, table_owner, table_name FROM all_synonyms WHERE synonym_name = 'RESTRICTED_PKG'; -- Check direct grants on the object SELECT grantor, grantee, privilege, grantable FROM dba_tab_privs WHERE table_name = 'RESTRICTED_PKG' AND grantee = 'CURRENT_SCHEMA' ORDER BY privilege;
Cascading Invalidation: ORA-06550 to ORA-04063
When a PL/SQL object fails compilation, Oracle marks it INVALID in USER_OBJECTS. Every object that depends on the invalid object is also marked INVALID automatically. This cascading invalidation is the mechanism that turns a single compilation error into a multi-team production outage.
The dependency chain flows downward through every reference type. If a package body is invalid, all procedures that call any procedure in that package become invalid. If a function is invalid, all views that reference the function become invalid. If a type is invalid, all tables that use the type and all procedures that reference the type become invalid.
The error that callers see is ORA-04063: package body 'X' has errors — not ORA-06550. This is critical for incident response. When ORA-04063 appears in production logs, the root cause is not in the failing procedure — it is in the invalid dependency. Query USER_ERRORS for the object named in the ORA-04063 message to find the original compilation error.
Oracle automatically revalidates dependent objects on next access once the base object compiles successfully. You do not need to manually recompile every dependent. However, revalidation happens lazily — each dependent is recompiled when it is first called, adding a brief latency to the first call. For production systems where this latency matters, use UTL_RECOMP.RECOMP_SERIAL or DBMS_UTILITY.COMPILE_SCHEMA to force immediate recompilation of all invalid objects.
-- ============================================================ -- Demonstrate cascading invalidation -- ============================================================ -- Base package — the root of the dependency tree CREATE OR REPLACE PACKAGE base_pkg AS FUNCTION get_tax_rate(p_region IN VARCHAR2) RETURN NUMBER; END base_pkg; / CREATE OR REPLACE PACKAGE BODY base_pkg AS FUNCTION get_tax_rate(p_region IN VARCHAR2) RETURN NUMBER IS BEGIN RETURN 0.08; END get_tax_rate; END base_pkg; / -- Dependent procedure — calls base_pkg CREATE OR REPLACE PROCEDURE calc_order_total( p_amount IN NUMBER, p_region IN VARCHAR2, p_total OUT NUMBER ) IS v_tax_rate NUMBER; BEGIN v_tax_rate := base_pkg.get_tax_rate(p_region); p_total := p_amount + (p_amount * v_tax_rate); END calc_order_total; / -- Dependent view — references a function from base_pkg CREATE OR REPLACE VIEW order_tax_view AS SELECT a.account_id, a.balance, base_pkg.get_tax_rate('US') AS tax_rate, a.balance * (1 + base_pkg.get_tax_rate('US')) AS total_with_tax FROM demo_accounts a; -- Verify: all objects are VALID SELECT object_name, object_type, status FROM user_objects WHERE object_name IN ('BASE_PKG', 'CALC_ORDER_TOTAL', 'ORDER_TAX_VIEW') ORDER BY object_type; -- Expected: all status = VALID -- ============================================================ -- Now break the base package — introduce a compilation error -- ============================================================ CREATE OR REPLACE PACKAGE BODY base_pkg AS FUNCTION get_tax_rate(p_region IN VARCHAR2) RETURN NUMBER IS BEGIN RETURN 0.08 -- Missing semicolon — ORA-06550 END get_tax_rate; END base_pkg; / -- Check: base_pkg is now INVALID -- Check: dependents are also INVALID SELECT object_name, object_type, status FROM user_objects WHERE object_name IN ('BASE_PKG', 'CALC_ORDER_TOTAL', 'ORDER_TAX_VIEW') ORDER BY object_type; -- Expected: all status = INVALID -- Try to call the dependent procedure — ORA-04063 DECLARE v_total NUMBER; BEGIN calc_order_total(100, 'US', v_total); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM); -- ORA-04063: package body "DEMO.BASE_PKG" has errors END; / -- ============================================================ -- Fix: correct the base package, recompile, revalidate -- ============================================================ -- Step 1: Find the root cause SELECT name, type, line, position, text FROM user_errors WHERE name = 'BASE_PKG' ORDER BY sequence; -- Step 2: Fix and recompile the base package CREATE OR REPLACE PACKAGE BODY base_pkg AS FUNCTION get_tax_rate(p_region IN VARCHAR2) RETURN NUMBER IS BEGIN RETURN 0.08; -- Fixed: semicolon added END get_tax_rate; END base_pkg; / -- Step 3: Verify base package is VALID SELECT status FROM user_objects WHERE object_name = 'BASE_PKG' AND object_type = 'PACKAGE BODY'; -- Step 4: Force revalidation of all INVALID objects -- Option A: UTL_RECOMP (recommended for production) EXEC UTL_RECOMP.RECOMP_SERIAL; -- Option B: DBMS_UTILITY.COMPILE_SCHEMA (recompiles everything) EXEC DBMS_UTILITY.COMPILE_SCHEMA(schema => USER, compile_all => FALSE); -- compile_all => FALSE: only recompiles INVALID objects -- Step 5: Verify all objects are VALID SELECT object_name, object_type, status FROM user_objects WHERE object_name IN ('BASE_PKG', 'CALC_ORDER_TOTAL', 'ORDER_TAX_VIEW') ORDER BY object_type; -- Expected: all status = VALID -- ============================================================ -- View the dependency tree before deploying changes -- Run this BEFORE deploying to estimate the blast radius -- ============================================================ SELECT name AS dependent_name, type AS dependent_type, referenced_name, referenced_type FROM user_dependencies WHERE referenced_name = 'BASE_PKG' ORDER BY type, name; -- Count dependents by type SELECT type, COUNT(*) AS dependent_count FROM user_dependencies WHERE referenced_name = 'BASE_PKG' GROUP BY type ORDER BY COUNT(*) DESC; -- Cleanup -- DROP VIEW order_tax_view; -- DROP PROCEDURE calc_order_total; -- DROP PACKAGE base_pkg;
CREATE OR REPLACE: The Silent Deployment Failure
CREATE OR REPLACE is the most dangerous deployment command in Oracle because it completes successfully even when compilation fails. The object is silently marked INVALID and the deployment script reports success. No exception is raised. No error message appears in the script output. The deployment log looks clean.
This behavior exists by design — Oracle allows creating invalid objects to support forward references and circular dependencies during development. Two packages that reference each other cannot both be valid during creation — one must be created first in an invalid state. CREATE OR REPLACE accommodates this by not raising on compilation failure.
But in deployment scripts, this behavior is dangerous. A typo, a missing grant, or a renamed column causes compilation failure. The deployment script finishes without error. The team marks the deployment as successful. Hours later, ORA-04063 errors begin appearing across the application as callers attempt to invoke the invalid object.
The fix is to never trust CREATE OR REPLACE success as an indicator of valid compilation. Always validate by checking USER_ERRORS after every CREATE OR REPLACE, or by using ALTER COMPILE which does raise on failure. SQLPlus SHOW ERRORS displays the last compilation errors — add it after every CREATE OR REPLACE in SQLPlus scripts.
-- ============================================================ -- UNSAFE: CREATE OR REPLACE with no validation -- The script reports success even if the object is INVALID -- ============================================================ CREATE OR REPLACE PROCEDURE unsafe_deploy_example IS v_count NUMBER -- Missing semicolon BEGIN NULL; END unsafe_deploy_example; / -- SQL*Plus output: "Procedure created." -- But the procedure is INVALID — no error is raised -- ============================================================ -- SAFE Pattern 1: SHOW ERRORS after every CREATE OR REPLACE -- For SQL*Plus and SQLcl deployment scripts -- ============================================================ CREATE OR REPLACE PROCEDURE safe_deploy_example IS v_count NUMBER; BEGIN SELECT COUNT(*) INTO v_count FROM demo_accounts; END safe_deploy_example; / SHOW ERRORS PROCEDURE SAFE_DEPLOY_EXAMPLE -- If compilation errors exist, they are displayed here -- Add WHENEVER SQLERROR EXIT to abort the script on errors -- ============================================================ -- SAFE Pattern 2: Query USER_ERRORS after deployment -- For programmatic deployment tools (Flyway, Liquibase, custom scripts) -- ============================================================ DECLARE v_error_count NUMBER; v_error_text VARCHAR2(4000); BEGIN SELECT COUNT(*) INTO v_error_count FROM user_errors WHERE name = 'SAFE_DEPLOY_EXAMPLE' AND type = 'PROCEDURE'; IF v_error_count > 0 THEN -- Get the first error for the exception message SELECT text INTO v_error_text FROM user_errors WHERE name = 'SAFE_DEPLOY_EXAMPLE' AND type = 'PROCEDURE' ORDER BY sequence FETCH FIRST 1 ROW ONLY; RAISE_APPLICATION_ERROR( -20060, 'Deployment failed: SAFE_DEPLOY_EXAMPLE has ' || v_error_count || ' compilation error(s). First error: ' || v_error_text ); END IF; DBMS_OUTPUT.PUT_LINE('SAFE_DEPLOY_EXAMPLE compiled successfully'); END; / -- ============================================================ -- SAFE Pattern 3: ALTER COMPILE — raises on failure -- Use for validation after CREATE OR REPLACE -- ============================================================ -- ALTER COMPILE raises an exception if compilation fails -- Unlike CREATE OR REPLACE, it does NOT complete silently ALTER PROCEDURE safe_deploy_example COMPILE; -- If successful: no output, object is VALID -- If failed: ORA-24344 or ORA-06550 is raised -- For packages: ALTER PACKAGE billing_pkg COMPILE; -- Compiles the spec ALTER PACKAGE billing_pkg COMPILE BODY; -- Compiles the body -- ============================================================ -- SAFE Pattern 4: Post-deployment health check -- Run after the entire deployment to catch any INVALID objects -- ============================================================ CREATE OR REPLACE PROCEDURE post_deployment_check IS v_invalid_count NUMBER; BEGIN SELECT COUNT(*) INTO v_invalid_count FROM user_objects WHERE status = 'INVALID'; IF v_invalid_count > 0 THEN FOR rec IN ( SELECT o.object_name, o.object_type, NVL((SELECT e.text FROM user_errors e WHERE e.name = o.object_name ORDER BY e.sequence FETCH FIRST 1 ROW ONLY), 'no error details') AS first_error FROM user_objects o WHERE o.status = 'INVALID' ORDER BY o.object_type, o.object_name ) LOOP DBMS_OUTPUT.PUT_LINE( 'INVALID: ' || rec.object_type || ' ' || rec.object_name || ' — ' || rec.first_error ); END LOOP; RAISE_APPLICATION_ERROR( -20061, 'Post-deployment check failed: ' || v_invalid_count || ' object(s) are INVALID. Check DBMS_OUTPUT for details.' ); END IF; DBMS_OUTPUT.PUT_LINE('Post-deployment check passed: all objects VALID'); END post_deployment_check; / -- Run after every deployment: EXEC post_deployment_check;
Privilege-Related Compilation: PLS-00904
PLS-00904: insufficient privilege to access object is the compilation error that costs the most debugging time. The code is correct. The referenced object exists. The name is spelled correctly. The compilation still fails because the compiling schema lacks the required privilege on the referenced object.
This error appears most frequently in multi-schema environments where packages in one schema reference tables or packages in another schema. The compiling schema needs EXECUTE privilege on referenced packages and SELECT privilege on referenced tables. Without these grants, compilation fails.
The critical subtlety is the distinction between definer's rights and invoker's rights. The default for PL/SQL packages is definer's rights — the package runs with the owner's privileges, and compilation checks the owner's direct grants. Role-based grants are invisible during compilation of definer's rights code. If the EXECUTE privilege is granted through a role but not directly, PLS-00904 fires.
Invoker's rights packages (created with AUTHID CURRENT_USER) can use role-based grants because they run with the calling user's privileges at execution time. The compilation still checks direct grants, but execution resolves references using the invoker's privilege set.
The fix for PLS-00904 is always a GRANT — never a code change. Issue the grant directly to the schema owner, not through a role, unless the package uses AUTHID CURRENT_USER.
-- ============================================================ -- PLS-00904 scenarios and fixes -- ============================================================ -- Scenario 1: Missing EXECUTE grant on another schema's package -- This procedure references FINANCE_SCHEMA.FINANCE_PKG CREATE OR REPLACE PROCEDURE call_finance_validation IS BEGIN finance_schema.finance_pkg.validate_account(12345); -- PLS-00904: insufficient privilege to access object FINANCE_PKG END call_finance_validation; / -- Fix: direct grant to the compiling schema -- Run as FINANCE_SCHEMA or a DBA: GRANT EXECUTE ON finance_schema.finance_pkg TO billing_schema; -- Then recompile: ALTER PROCEDURE call_finance_validation COMPILE; -- ============================================================ -- Scenario 2: Role-based grant does NOT work for definer's rights -- ============================================================ -- This grant goes through a role: GRANT EXECUTE ON finance_schema.finance_pkg TO app_role; GRANT app_role TO billing_schema; -- billing_schema has the privilege via the role -- But definer's rights compilation STILL raises PLS-00904 -- The fix: use a direct grant, or switch to invoker's rights -- ============================================================ -- Scenario 3: Invoker's rights — role-based grants work -- ============================================================ CREATE OR REPLACE PROCEDURE call_finance_validation_ir AUTHID CURRENT_USER -- Invoker's rights IS BEGIN finance_schema.finance_pkg.validate_account(12345); -- Resolves at execution time using the caller's privileges -- Role-based grants work because they are active during execution END call_finance_validation_ir; / -- ============================================================ -- Diagnostic: find all PLS-00904 errors in the schema -- ============================================================ SELECT name, type, line, text FROM user_errors WHERE text LIKE '%PLS-00904%' ORDER BY name, sequence; -- ============================================================ -- Diagnostic: check what direct grants exist for the compiling schema -- ============================================================ SELECT grantor, privilege, table_name AS object_name, grantable FROM dba_tab_privs WHERE grantee = 'BILLING_SCHEMA' AND privilege = 'EXECUTE' ORDER BY table_name; -- Check role-based grants (will not help for definer's rights) SELECT granted_role, admin_option FROM dba_role_privs WHERE grantee = 'BILLING_SCHEMA' ORDER BY granted_role;
- Definer's rights (default): compiles using the owner's direct grants only. Role-based grants are invisible. PLS-00904 fires if the direct grant is missing.
- Invoker's rights (AUTHID CURRENT_USER): compiles checking direct grants, but executes using the caller's full privilege set including roles.
- PLS-00904 during compilation means the compiling schema lacks a direct EXECUTE or SELECT grant on a referenced object.
- The fix is always a GRANT, never a code change. Check dba_tab_privs before debugging the procedure body.
- If converting to invoker's rights, remember that the procedure now runs with different privileges for each caller — test accordingly.
Pre-Deployment Compilation Validation and CI/CD
Every deployment pipeline should treat PL/SQL compilation as a build step that can fail. The pattern is the same as any compiled language: compile, check for errors, abort if any are found.
For SQL*Plus deployment scripts, add WHENEVER SQLERROR EXIT FAILURE at the top and SHOW ERRORS after every CREATE OR REPLACE. This combination causes the script to abort on the first error instead of continuing blind.
For CI/CD pipelines (Jenkins, GitHub Actions, GitLab CI), add a post-deployment step that runs a validation procedure: query USER_OBJECTS for INVALID status, query USER_ERRORS for compilation errors, and fail the pipeline if any are found. The pipeline should not mark the deployment as successful until all objects are VALID.
For staging environments, compile all PL/SQL objects in a clean schema that mirrors production. If any object fails compilation, block the deployment. This catches ORA-06550 before it reaches production — including privilege errors that only appear in the production grant model.
UTL_RECOMP.RECOMP_SERIAL is the recommended utility for batch recompilation in production. It recompiles all INVALID objects in dependency order, ensuring that base objects are compiled before their dependents. DBMS_UTILITY.COMPILE_SCHEMA is an alternative that accepts a compile_all parameter — set it to FALSE to recompile only INVALID objects.
-- ============================================================ -- SQL*Plus deployment script with compilation validation -- ============================================================ -- Abort script on any error WHENEVER SQLERROR EXIT FAILURE ROLLBACK SET SERVEROUTPUT ON PROMPT Deploying BILLING_PKG specification... CREATE OR REPLACE PACKAGE billing_pkg AS PROCEDURE process_payment(p_account_id IN NUMBER, p_amount IN NUMBER); FUNCTION get_balance(p_account_id IN NUMBER) RETURN NUMBER; END billing_pkg; / SHOW ERRORS PACKAGE BILLING_PKG PROMPT Deploying BILLING_PKG body... CREATE OR REPLACE PACKAGE BODY billing_pkg AS PROCEDURE process_payment(p_account_id IN NUMBER, p_amount IN NUMBER) IS v_balance NUMBER; BEGIN v_balance := get_balance(p_account_id); IF v_balance < p_amount THEN RAISE_APPLICATION_ERROR(-20001, 'Insufficient balance'); END IF; UPDATE demo_accounts SET balance = balance - p_amount WHERE account_id = p_account_id; END process_payment; FUNCTION get_balance(p_account_id IN NUMBER) RETURN NUMBER IS v_balance NUMBER; BEGIN SELECT balance INTO v_balance FROM demo_accounts WHERE account_id = p_account_id; RETURN v_balance; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20002, 'Account not found: ' || p_account_id); END get_balance; END billing_pkg; / SHOW ERRORS PACKAGE BODY BILLING_PKG -- ============================================================ -- Post-deployment validation — run after all objects are deployed -- ============================================================ PROMPT Running post-deployment validation... DECLARE v_invalid_count NUMBER; BEGIN -- Recompile any remaining INVALID objects UTL_RECOMP.RECOMP_SERIAL; -- Check for any remaining INVALID objects SELECT COUNT(*) INTO v_invalid_count FROM user_objects WHERE status = 'INVALID'; IF v_invalid_count > 0 THEN DBMS_OUTPUT.PUT_LINE('WARNING: ' || v_invalid_count || ' INVALID objects remain:'); FOR rec IN ( SELECT object_name, object_type FROM user_objects WHERE status = 'INVALID' ORDER BY object_type, object_name ) LOOP DBMS_OUTPUT.PUT_LINE(' ' || rec.object_type || ' ' || rec.object_name); END LOOP; RAISE_APPLICATION_ERROR( -20062, 'Post-deployment validation failed: ' || v_invalid_count || ' INVALID objects' ); END IF; DBMS_OUTPUT.PUT_LINE('Post-deployment validation passed: all objects VALID'); END; / PROMPT Deployment complete. -- ============================================================ -- CI/CD pipeline compilation check (conceptual — adapt to your pipeline) -- Run in a staging schema before deploying to production -- ============================================================ -- Step 1: Deploy all objects to the staging schema -- (handled by your CI/CD tool — Flyway, Liquibase, custom scripts) -- Step 2: Compile all objects and check for errors BEGIN UTL_RECOMP.RECOMP_SERIAL; END; / -- Step 3: Fail the pipeline if any objects are INVALID DECLARE v_invalid_count NUMBER; BEGIN SELECT COUNT(*) INTO v_invalid_count FROM user_objects WHERE status = 'INVALID' AND object_type IN ('PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER'); IF v_invalid_count > 0 THEN -- Output errors for the CI log FOR rec IN ( SELECT o.object_name, o.object_type, e.line, e.text AS error_text FROM user_objects o LEFT JOIN user_errors e ON e.name = o.object_name AND e.type = o.object_type WHERE o.status = 'INVALID' ORDER BY o.object_name, e.sequence ) LOOP DBMS_OUTPUT.PUT_LINE( rec.object_type || ' ' || rec.object_name || CASE WHEN rec.line IS NOT NULL THEN ' Line ' || rec.line || ': ' || rec.error_text ELSE ' (no error details — dependency invalidation)' END ); END LOOP; RAISE_APPLICATION_ERROR(-20063, 'CI compilation check failed'); END IF; END; /
- SQL*Plus scripts: add WHENEVER SQLERROR EXIT FAILURE at the top and SHOW ERRORS after every CREATE OR REPLACE
- Programmatic tools: query USER_ERRORS after every CREATE OR REPLACE and abort if any errors exist
- Post-deployment: run UTL_RECOMP.RECOMP_SERIAL to revalidate any cascading invalidations, then check USER_OBJECTS for remaining INVALID objects
- CI/CD pipeline: compile all objects in a staging schema before deploying to production — fail the pipeline on any INVALID object
- Never mark a deployment as successful without confirming zero INVALID objects in the target schema
ORA-06550 Through Application Drivers
Application developers encounter ORA-06550 in two distinct contexts. The first is when executing an anonymous PL/SQL block that contains a compilation error. The second — more common in production — is when calling a stored procedure that depends on an INVALID object, producing ORA-04063 or ORA-06508.
ORA-06550 from an anonymous block appears in the application driver as a standard database error with the full error text preserved. The line and column numbers reference the anonymous block text, not a stored object.
ORA-04063 and ORA-06508 appear when the application calls a stored procedure whose dependency tree contains an INVALID object. The error message includes the invalid object name. The application team sees the call fail but cannot see the underlying compilation error — only the DBA or deployment team can query USER_ERRORS for the root cause.
In a well-designed application stack, ORA-04063 should trigger an immediate alert to the database or deployment team — it indicates a deployment issue, not an application bug. The application layer should not retry ORA-04063 — it will not resolve on its own. Log the error with full context and escalate.
============================================================ JAVA / JDBC — ORA-06550 and ORA-04063 handling ============================================================ try { CallableStatement cs = conn.prepareCall( "BEGIN billing_pkg.process_payment(?, ?); END;" ); cs.setInt(1, accountId); cs.setBigDecimal(2, amount); cs.execute(); } catch (SQLException e) { switch (e.getErrorCode()) { case 6550: // ORA-06550: compilation error in an anonymous block // Usually a bug in the dynamic SQL construction logger.error("PL/SQL compilation error", Map.of( "oraCode", e.getErrorCode(), "message", e.getMessage() // Contains line, column, PLS code )); throw new ApplicationBugException("Dynamic PL/SQL compilation failed", e); case 4063: // ORA-04063: package body has errors // A stored object is INVALID — deployment or DDL issue // Do NOT retry — this will not resolve on its own logger.error("Database object INVALID — deployment issue", Map.of( "oraCode", e.getErrorCode(), "message", e.getMessage() )); alertingService.critical("ORA-04063: " + e.getMessage()); throw new DeploymentException("Database object is invalid", e); case 6508: // ORA-06508: could not find program unit being called // The package or procedure does not exist or is INVALID logger.error("Program unit not found", Map.of( "oraCode", e.getErrorCode(), "message", e.getMessage() )); alertingService.critical("ORA-06508: " + e.getMessage()); throw new DeploymentException("Program unit not found", e); default: throw e; } } ============================================================ PYTHON / python-oracledb ============================================================ import oracledb try: cursor.callproc('billing_pkg.process_payment', [account_id, amount]) except oracledb.DatabaseError as e: error = e.args[0] if error.code == 6550: # Compilation error in dynamic PL/SQL logger.error('PL/SQL compilation error', extra={ 'ora_code': error.code, 'message': error.message}) raise ApplicationBugError('Dynamic PL/SQL failed') from e elif error.code in (4063, 6508): # Database object is INVALID — escalate immediately logger.error('Database object INVALID', extra={ 'ora_code': error.code, 'message': error.message}) alert_team('critical', f'ORA-{error.code}: {error.message}') raise DeploymentError('Invalid database object') from e else: raise ============================================================ NODE.JS / node-oracledb ============================================================ try { await connection.execute( 'BEGIN billing_pkg.process_payment(:id, :amt); END;', { id: accountId, amt: amount } ); } catch (err) { if (err.errorNum === 6550) { console.error(JSON.stringify({ level: 'error', oraCode: err.errorNum, message: err.message, context: 'dynamic PL/SQL' })); throw new ApplicationBugError('Dynamic PL/SQL failed', { cause: err }); } else if ([4063, 6508].includes(err.errorNum)) { console.error(JSON.stringify({ level: 'critical', oraCode: err.errorNum, message: err.message })); await alertingService.critical(`ORA-${err.errorNum}: ${err.message}`); throw new DeploymentError('Invalid database object', { cause: err }); } else { throw err; } } ============================================================ Key points: ============================================================ 1. ORA-06550: compilation error in dynamic PL/SQL — usually a code bug 2. ORA-04063: stored object is INVALID — deployment/DDL issue, escalate 3. ORA-06508: program unit not found — object missing or INVALID, escalate 4. Do NOT retry ORA-04063 or ORA-06508 — they will not self-resolve 5. Classify ORA-04063 as a deployment error, not an application error 6. Log the full Oracle error message — it contains the INVALID object name
Core Billing Package Invalidated After Silent ORA-06550 — 47 Dependent Objects Offline for 2 Hours
- CREATE OR REPLACE never raises an exception on compilation failure — it silently marks the object INVALID and reports success to the calling script
- ORA-04063 in production logs means a dependent object is INVALID — the root cause is an ORA-06550 compilation failure in the base object, found by querying USER_ERRORS
- A single invalid package body cascades to every object in its dependency tree — the blast radius can be dozens of objects across multiple schemas
- Every deployment script must validate compilation by checking USER_ERRORS and USER_OBJECTS — trusting CREATE OR REPLACE success is deploying blind
SELECT line, position, text FROM user_errors WHERE name = 'BILLING_PKG' AND type = 'PACKAGE BODY' ORDER BY sequence;SHOW ERRORS PACKAGE BODY BILLING_PKGSELECT object_name, object_type, last_ddl_time FROM user_objects WHERE status = 'INVALID' ORDER BY object_type, object_name;SELECT COUNT(*) AS invalid_count, object_type FROM user_objects WHERE status = 'INVALID' GROUP BY object_type ORDER BY COUNT(*) DESC;SELECT name, type, referenced_name, referenced_type FROM user_dependencies WHERE referenced_name = 'BILLING_PKG' ORDER BY type, name;SELECT COUNT(*) AS dependent_count, type FROM user_dependencies WHERE referenced_name = 'BILLING_PKG' GROUP BY type ORDER BY COUNT(*) DESC;SELECT grantor, privilege, grantable FROM dba_tab_privs WHERE grantee = 'BILLING_SCHEMA' AND table_name = 'RESTRICTED_PKG';SELECT granted_role FROM dba_role_privs WHERE grantee = 'BILLING_SCHEMA';| PLS Code | Category | Typical Cause | Resolution | Diagnostic Query |
|---|---|---|---|---|
| PLS-00103 | Syntax | Missing semicolon, unmatched parenthesis, invalid keyword, malformed expression | Fix the code grammar at the reported line and column — the error message shows what was found and what was expected | N/A — fix directly in source code |
| PLS-00201 | Semantic | Undeclared variable, missing table, missing synonym, misspelled identifier, object not yet created | Declare the variable, create the object, create a synonym, or correct the spelling | SELECT object_name FROM all_objects WHERE object_name = 'NAME' |
| PLS-00302 | Semantic | Field or method does not exist on the referenced record type, package, or object type | Check the type definition — the field was renamed, removed, or never existed | SELECT text FROM user_source WHERE name = 'TYPE_NAME' AND type IN ('PACKAGE', 'TYPE') |
| PLS-00306 | Semantic | Wrong number or types of arguments in a procedure or function call | Compare the call against the procedure specification — match parameter count and types | DESC schema.package to view the specification |
| PLS-00904 | Privilege | Missing EXECUTE or SELECT grant on a referenced object in another schema | Grant the required privilege directly to the compiling schema — role grants do not work for definer's rights | SELECT * FROM dba_tab_privs WHERE grantee = 'SCHEMA' AND table_name = 'OBJECT' |
| PLS-00905 | Semantic | Object in another schema is INVALID — cannot be referenced until it compiles | Fix and recompile the referenced object in the other schema first | SELECT status FROM all_objects WHERE owner = 'OTHER' AND object_name = 'NAME' |
| PLS-00410 | Semantic | Duplicate column name in a record declaration or SELECT INTO target list | Rename duplicate columns or use aliases in the SELECT list | Review the SELECT list for duplicate column names |
| File | Command / Code | Purpose |
|---|---|---|
| ora06550_examples.sql | CREATE TABLE demo_accounts ( | ORA-06550 Anatomy |
| syntax_vs_semantic.sql | CREATE OR REPLACE PROCEDURE demo_missing_semicolon IS | Syntax Errors vs Semantic Errors |
| cascading_invalidation.sql | CREATE OR REPLACE PACKAGE base_pkg AS | Cascading Invalidation |
| safe_deployment.sql | CREATE OR REPLACE PROCEDURE unsafe_deploy_example IS | CREATE OR REPLACE |
| privilege_compilation.sql | CREATE OR REPLACE PROCEDURE call_finance_validation IS | Privilege-Related Compilation |
| cicd_validation.sql | WHENEVER SQLERROR EXIT FAILURE ROLLBACK | Pre-Deployment Compilation Validation and CI/CD |
| driver_compilation_errors.txt | ============================================================ | ORA-06550 Through Application Drivers |
Key takeaways
Common mistakes to avoid
6 patternsTrusting CREATE OR REPLACE success as confirmation that the object compiled correctly
Fixing only the first compilation error and assuming subsequent errors are related
Debugging PLS-00904 as a code error instead of checking grants
Deploying to a core package without checking the dependency tree first
Using role-based grants for cross-schema references in definer's rights packages
Not running UTL_RECOMP after fixing a compilation error
Interview Questions on This Topic
What is ORA-06550 and how does it differ from runtime errors like ORA-01403?
Why does CREATE OR REPLACE not raise an exception when compilation fails, and what should you do about it?
How does ORA-06550 cascade to ORA-04063 in production, and how do you diagnose the root cause?
What is PLS-00904 and why does it require a different debugging approach than other PLS errors?
How would you design a CI/CD pipeline to prevent ORA-06550 from reaching production?
Frequently Asked Questions
ORA-06550 is the original compilation error — it occurs when the PL/SQL compiler encounters a syntax or semantic issue. ORA-04063 is the error that callers see when they try to invoke an object that is INVALID due to a prior compilation failure. ORA-06550 contains the line, column, and PLS error code. ORA-04063 contains only the invalid object name. To find the root cause of ORA-04063, query USER_ERRORS for the object named in the message.
Oracle allows creating invalid objects to support forward references and circular dependencies during development. Two packages that reference each other cannot both be valid during creation. CREATE OR REPLACE accommodates this by completing silently and marking the object INVALID. This is safe during development but dangerous in deployment scripts. Always validate compilation after CREATE OR REPLACE by checking USER_ERRORS.
Query USER_OBJECTS: SELECT object_name, object_type FROM user_objects WHERE status = 'INVALID' ORDER BY object_type, object_name. For each INVALID object, query USER_ERRORS to find its compilation errors: SELECT line, position, text FROM user_errors WHERE name = 'OBJECT_NAME' ORDER BY sequence. Objects that appear in USER_OBJECTS as INVALID but have no rows in USER_ERRORS were invalidated by a dependency — fix their dependency first.
Only for invoker's rights procedures (AUTHID CURRENT_USER). The default PL/SQL authorization model is definer's rights, which checks only direct grants during compilation — role-based grants are invisible. If PLS-00904 appears despite a role grant, issue a direct grant: GRANT EXECUTE ON schema.object TO compiling_schema.
Use UTL_RECOMP.RECOMP_SERIAL — it recompiles all INVALID objects in the correct dependency order. Alternatively, DBMS_UTILITY.COMPILE_SCHEMA(schema => USER, compile_all => FALSE) recompiles only INVALID objects in the specified schema. After recompilation, verify: SELECT COUNT(*) FROM user_objects WHERE status = 'INVALID' — should return zero.
PLS-00103 means the compiler encountered an unexpected token — a syntax error. The error message shows both what was found and what was expected. For example: 'Encountered the symbol BEGIN when expecting one of the following: ;' means a semicolon is missing before the BEGIN keyword. Fix by correcting the grammar at the reported line and column. The most common causes are missing semicolons, unmatched parentheses, and invalid keywords.
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
That's PL/SQL. Mark it forged?
7 min read · try the examples if you haven't