ORA-06550: PL/SQL Compilation Error – The Complete Guide
- ORA-06550 is a compile-time error — the object is marked INVALID and nothing executes until it is fixed
- The PLS error code after ORA-06550 is the actual diagnosis: PLS-00103 = syntax, PLS-00201 = missing identifier, PLS-00904 = missing privilege
- CREATE OR REPLACE silently marks objects INVALID on compilation failure — always validate with USER_ERRORS, SHOW ERRORS, or ALTER COMPILE
- 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
Need to see compilation errors for a specific object
SELECT line, position, text FROM user_errors WHERE name = 'BILLING_PKG' AND type = 'PACKAGE BODY' ORDER BY sequence;SHOW ERRORS PACKAGE BODY BILLING_PKGNeed to find all INVALID objects in the schema
SELECT 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;Need to see what objects depend on a specific package before deploying a change
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;Need to check if a missing privilege is causing PLS-00904
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';Production Incident
Production Debug GuideFrom compilation error to deployed fix
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.
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
| 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 |
🎯 Key Takeaways
- ORA-06550 is a compile-time error — the object is marked INVALID and nothing executes until it is fixed
- The PLS error code after ORA-06550 is the actual diagnosis: PLS-00103 = syntax, PLS-00201 = missing identifier, PLS-00904 = missing privilege
- CREATE OR REPLACE silently marks objects INVALID on compilation failure — always validate with USER_ERRORS, SHOW ERRORS, or ALTER COMPILE
- A single compilation failure cascades through the entire dependency tree — callers see ORA-04063, not ORA-06550
- PLS-00904 is a privilege error that looks like a code bug — check dba_tab_privs before debugging the procedure body
- Definer's rights packages require direct grants — role-based grants are invisible during compilation
- Run UTL_RECOMP.RECOMP_SERIAL after fixing the root cause to force immediate revalidation of all INVALID dependents
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is ORA-06550 and how does it differ from runtime errors like ORA-01403?JuniorReveal
- QWhy does CREATE OR REPLACE not raise an exception when compilation fails, and what should you do about it?Mid-levelReveal
- QHow does ORA-06550 cascade to ORA-04063 in production, and how do you diagnose the root cause?Mid-levelReveal
- QWhat is PLS-00904 and why does it require a different debugging approach than other PLS errors?SeniorReveal
- QHow would you design a CI/CD pipeline to prevent ORA-06550 from reaching production?SeniorReveal
Frequently Asked Questions
What is the difference between ORA-06550 and ORA-04063?
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.
Why does CREATE OR REPLACE not raise an error when compilation fails?
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.
How do I find all INVALID objects in my schema?
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.
Can I use role-based grants for PL/SQL compilation?
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.
How do I recompile all INVALID objects after fixing a compilation error?
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.
What does PLS-00103 mean and how do I fix it?
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.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.