Oracle Database 26ai introduces DBMS_DEVELOPER, a package that lets applications and developer tools retrieve structured metadata about database objects in JSON format. Starting with Oracle Database 19c Release Update 19.28, it is also available in Oracle Database 19c.
As you know, Oracle provides several ways to provide ways to retrieve metadata and inspect database objects such as SQL*Plus, SQL Developer, Oracle SQL Developer for VS Code, and SQLcl. For example, SQLcl provides commands such as INFO and DDL:
SQL> info hr.countries
The output looks like …
TABLE: COUNTRIES
LAST ANALYZED:2025-02-05 16:20:42.0
ROWS :25
SAMPLE SIZE :25
INMEMORY :DISABLED
COMMENTS :country table. References with locations table.
Columns
NAME DATA TYPE NULL DEFAULT COMMENTS
*COUNTRY_ID CHAR(2 BYTE) No Primary key of countries table.
COUNTRY_NAME VARCHAR2(60 BYTE) Yes displayed
REGION_ID NUMBER Yes Region ID for the country. Foreign key to
region_id column in the departments table.
Indexes
INDEX_NAME UNIQUENESS STATUS FUNCIDX_STATUS COLUMNS
_____________________ _____________ _________ _________________ _____________
HR.COUNTRY_C_ID_PK UNIQUE VALID COUNTRY_ID
References
TABLE_NAME CONSTRAINT_NAME DELETE_RULE STATUS DEFERRABLE VALIDATED GENERATED
_____________ __________________ ______________ __________ _________________ ___________ ____________
LOCATIONS LOC_C_ID_FK NO ACTION ENABLED NOT DEFERRABLE VALIDATED USER NAME
You can also use DDL to generate the object definition:
SQL> ddl hr.countries
This is the output:
CREATE TABLE "HR"."COUNTRIES"
("COUNTRY_ID" CHAR(2) COLLATE "USING_NLS_COMP" CONSTRAINT "COUNTRY_ID_NN" NOT NULL ENABLE,
"COUNTRY_NAME" VARCHAR2(60) COLLATE "USING_NLS_COMP",
"REGION_ID" NUMBER,
CONSTRAINT "COUNTRY_C_ID_PK" PRIMARY KEY ("COUNTRY_ID") ENABLE, CONSTRAINT "COUNTR_REG_FK" FOREIGN KEY ("REGION_ID")
REFERENCES "HR"."REGIONS" ("REGION_ID") ENABLE
) DEFAULT COLLATION "USING_NLS_COMP" SEGMENT CREATION IMMEDIATE
ORGANIZATION INDEX NOCOMPRESS PCTFREE 10 INITRANS 2 MAXTRANS 255 LOGGING STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"
PCTTHRESHOLD 50;
COMMENT ON COLUMN "HR"."COUNTRIES"."COUNTRY_ID" IS 'Primary key of countries table.';
COMMENT ON COLUMN "HR"."COUNTRIES"."COUNTRY_NAME" IS 'displayed';
COMMENT ON COLUMN "HR"."COUNTRIES"."REGION_ID" IS 'Region ID for the country. Foreign key to region_id column in the departments table.';
COMMENT ON TABLE "HR"."COUNTRIES" IS 'country table. References with locations table.';
Operation completed successfully
Another important option is the PL/SQL package DBMS_METADATA. It is particularly useful when you need object definitions in DDL or XML format, or when you want to use the metadata to recreate an object. For more information, see the Further Readings section below.
So, what about this new DBMS_DEVELOPER package? In the sections below, we will explore how to use it and demonstrate its capabilities with several practical examples.
This post is organized into the following sections:
- Getting started with DBMS_DEVELOPER
- Retrieving Metadata for Tables at the
BASICLevel - Retrieving Metadata for Tables at the
TYPICALLevel - Using ETAG with GET_METADATA
- Retrieving Metadata for Views at the
ALLLevel - Retrieving Metadata for JSON Duality Views at the
ALLLevel
Getting started with DBMS_DEVELOPER
You might now be wondering: why use the new DBMS_DEVELOPER package? Here are some of its key advantages:
- Very fast retrieval
- Structured JSON output
- Documented JSON schema for the supported object types and detail levels
- Least-privilege access, typically
SELECTorREAD - Change detection using the ETAG parameter
- One interface across object types and different detail levels
These characteristics make DBMS_DEVELOPER especially useful for IDEs, database browsers, code-generation tools, automation, and other applications that need up-to-date database object metadata.
Currently, the package includes a single function: GET_METADATA. You provide the information needed to identify the object, and the function returns a JSON document containing its metadata. The syntax is as follows:
DBMS_DEVELOPER.GET_METADATA ( name IN VARCHAR2, schema IN VARCHAR2 DEFAULT NULL, object_type IN VARCHAR2 DEFAULT NULL, level IN VARCHAR2 DEFAULT 'TYPICAL' etag IN RAW DEFAULT NULL) RETURN JSON;
So, how do you use it? The object_type argument currently supports TABLE, INDEX, and VIEW. The level argument controls how much detail is returned: BASIC, TYPICAL, or ALL. The etag argument identifies a specific version of the document, allowing an application to determine whether two document versions contain the same content.
The structure of the returned JSON depends on the selected object type and detail level. Oracle documents the corresponding JSON schemas in the following tables:
- Table 75-2 JSON schema for object type : TABLE
- Table 75-5 JSON Schema for COLUMNS Sub-Object
- Table 75-6 JSON Schema for Sub-Object Type : CONSTRAINTS
- Table 75-4 JSON Schema for INDEX Object Type
- Table 75-3 JSON Schema for VIEW Object Type
Let’s start with a simple example using a nonprivileged user named DEVMETA. This user has only the privileges needed for our examples, including select privilege for the HR.COUNTRIES table, the HR.EMP_VIEW_DETAILS view, and the CHUNKS_DV JSON duality view.
If you don’t already have this user, create it as follows:
create user devmeta identified by '&password';
grant connect to devmeta;
grant select on &objectname to devmeta;
Retrieving Metadata for Tables at the BASIC Level
For our first example, we’ll retrieve metadata for the HR.COUNTRIES table at the BASIC level. To make the output easier to read, we’ll use JSON_SERIALIZE with the PRETTY option, which formats the JSON document as human-readable text.
SET LONG 10000 HEADING OFF
select JSON_SERIALIZE(
DBMS_DEVELOPER.GET_METADATA(
schema => 'HR',
name => 'COUNTRIES',
level => 'BASIC' ) pretty) result;
Executing this query produces the following JSON document:
{
"objectType" : "TABLE",
"objectInfo" :
{
"name" : "COUNTRIES",
"schema" : "HR",
"columns" :
[
{
"name" : "COUNTRY_ID",
"notNull" : true,
"dataType" :
{
"type" : "CHAR",
"length" : 2,
"sizeUnits" : "BYTE"
}
},
{
"name" : "COUNTRY_NAME",
"notNull" : false,
"dataType" :
{
"type" : "VARCHAR2",
"length" : 60,
"sizeUnits" : "BYTE"
}
},
{
"name" : "REGION_ID",
"notNull" : false,
"dataType" :
{
"type" : "NUMBER"
}
}
]
},
"etag" : "C430DC814B06E0408ADF8515377CD2CF"
}
The resulting JSON document at level BASIC is based on the following JSON schema for the object type TABLE (see also the documentation).
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id":"https://oracle.com/schema/23.26.2/DBMS_DEVELOPER.GET_METADATA/BASIC/object_info/table",
"title": "DBMS_DEVELOPER.GET_METADATA/BASIC/OBJECT_INFO/TABLE",
"description": "Information for a table object",
"type": "object",
"properties": {
"name": {
"description": "Table name",
"type": "string"
},
"schema": {
"description": "Table schema",
"type": "string"
},
"columns": {
"description": "Table columns",
"type": "array",
"items": {
"$ref": "../column"
}
}
},
"required": [
"name",
"schema",
"columns"
]
}
Retrieving Metadata for Tables at the TYPICAL Level
Now let’s try out the default value TYPICAL. As you can see we do receive more information such as constraints, number of rows, indexes etc.
SET LONG 10000 HEADING OFF
select JSON_SERIALIZE(
DBMS_DEVELOPER.GET_METADATA(
schema => 'HR',
name => 'COUNTRIES',
level => 'TYPICAL' ) pretty) result;
Here is an excerpt of the resulting JSON document.
...
{
{
"name" : "REGION_ID",
"notNull" : false,
"dataType" :
{
"type" : "NUMBER"
},
"isPk" : false,
"isUk" : false,
"isFk" : true
}
],
"numRows" : 25,
"external" : "NO",
"constraints" :
[
{
"name" : "COUNTRY_ID_NN",
"constraintType" : "CHECK - NOT NULL",
"searchCondition" : "\"COUNTRY_ID\" IS NOT NULL",
"columns" :
[
...
"etag" : "C430DC814B06E0408ADF8515377CD2CF" }
USING ETAG with GET_METADATA
Let’s see how ETAG works in practice. An ETAG identifies a specific version of an object’s metadata and allows an application to determine whether that metadata has changed since a previous GET_METADATA call.
Therefore, the rules are as follows:
- If the supplied ETAG matches the current metadata ETAG, GET_METADATA returns an empty JSON document.
- If the supplied ETAG does not match, GET_METADATA returns the current metadata document, including the updated ETAG.
To demonstrate this behavior, the HR user will first modify the COUNTRIES table by adding an annotation to the COUNTRY_NAME column:
alter table hr.countries modify country_name annotations (add display 'CNAME');
This change updates the table’s metadata and therefore produces a new ETAG. Reconnect as DEVMETA and call DBMS_DEVELOPER.GET_METADATA again, leaving the level argument at its default value, TYPICAL. Then compare the returned ETAG with the value from the previous call.
SET LONG 10000 HEADING OFF
select JSON_SERIALIZE(
DBMS_DEVELOPER.GET_METADATA(
schema => 'HR',
name => 'COUNTRIES') pretty) result;
{
"objectType" : "TABLE",
"objectInfo" :
{
"schema" : "HR",
"columns" :
[
{
"name" : "COUNTRY_ID",
"notNull" : true,
"dataType" :
{
"type" : "CHAR",
"length" : 2,
"sizeUnits" : "BYTE"
},
"isPk" : true,
"isUk" : true,
"isFk" : false
},
...
],
"lastAnalyzed" : "2026-09-16T09:40:51",
"sampleSize" : 25,
"indexes" :
[
{
"name" : "COUNTRY_C_ID_PK",
"indexType" : "NORMAL",
"uniqueness" : "UNIQUE",
"status" : "VALID",
"lastAnalyzed" : "2026-09-16T09:40:51",
"numRows" : 25,
"sampleSize" : 25,
"partitioned" : false,
"columns" :
[
{
"name" : "COUNTRY_ID"
}
]
}
],
"avgRowLen" : 16,
"name" : "COUNTRIES",
"temporary" : "NO"
},
"etag" : "A217DC0C71E237A0E751ECABA7D1FB1A"
}
The returned document includes the new annotation, and its current ETAG value “A217DC0C71E237A0E751ECABA7D1FB1A”.
Now pass the current ETAG value to the GET_METADATA.
select JSON_SERIALIZE(
DBMS_DEVELOPER.GET_METADATA(
schema => 'HR',
object_type => 'TABLE',
name => 'COUNTRIES',
etag => 'A217DC0C71E237A0E751ECABA7D1FB1A' )
returning clob pretty) result;
RESULT
-------------------------------------------------------------------------------- { }
Because the supplied ETAG matches the current value, the function returns an empty JSON document {}.
Retrieving Metadata for Views at the ALL Level
In the next example, let’s use the view EMP_DETAILS_VIEW from schema HR at level ALL. Information about referenced objects is added to the output (e.g. information about tables referenced by a view).
SET LONG 10000 HEADING OFF
select JSON_SERIALIZE (
DBMS_DEVELOPER.GET_METADATA
(schema => 'HR',
object_type => 'VIEW',
name => 'EMP_DETAILS_VIEW',
level => 'ALL' )
returning clob pretty) result;
...
"constraints" :
[
{
"name" : "SYS_C0037613",
"constraintType" : "VIEW READONLY",
"status" : "ENABLED",
"deferrable" : false,
"validated" : "NOT VALIDATED",
"sysGeneratedName" : true
}
],
"references" :
[
{
"schema" : "HR",
"name" : "COUNTRIES",
"objectType" : "TABLE"
}
],
"editioningView" : false,
...
Retrieving Metadata for JSON Duality Views at the ALL Level
In this final example, let’s look at a JSON Duality View. Assume that the view CHUNKS_DV has been created in the AIUSER schema. When you request metadata at the ALL level, the result includes detailed information about the view, including the JSON schema that describes the structure of the documents it supports.
You can retrieve this schema separately by using DBMS_JSON_DESCRIBE function.
select JSON_SERIALIZE (DBMS_JSON_SCHEMA.DESCRIBE('CHUNKS_DV')pretty) result;
Here is an excerpt of the result:
{
"title" : "CHUNKS_DV",
"dbObject" : "AIUSER.CHUNKS_DV",
"dbObjectType" : "dualityView",
"dbObjectProperties" :
[
"insert",
"update",
"delete",
"check"
],
"type" : "object",
"properties" :
{
"_id" :
{
"type" : "number",
"extendedType" : "number",
"dbAssigned" : true,
"dbFieldProperties" :
[
"check"
]
},
"_metadata" :
{
"etag" :
{
"type" : "string",
"extendedType" : "string",
"maxLength" : 200
},
"asof" :
{
"type" : "string",
"extendedType" : "string",
"maxLength" : 20
}
},
"name" :
{
"type" : "string",
"extendedType" : "string",
"maxLength" : 500,
"dbFieldProperties" :
[
"update",
"check"
]
...
You can also retrieve the JSON schema together with the view’s other metadata by using DBMS_DEVELOPER.GET_METADATA.
select JSON_SERIALIZE (DBMS_DEVELOPER.GET_METADATA
(schema => 'AIUSER',
object_type => 'VIEW',
name => 'CHUNKS_DV',
level => 'all' )
returning clob pretty) result;
Because a JSON Duality View is represented as a VIEW object, specify object_type => 'VIEW'. With level => 'ALL', GET_METADATA returns the most comprehensive metadata available, including the JSON schema, in a single call.
{
"objectType" : "VIEW",
"objectInfo" :
{
"name" : "CHUNKS_DV",
"schema" : "AIUSER",
"columns" :
[
{
"name" : "DATA",
"notNull" : false,
"dataType" :
{
...
],
"jsonSchema" :
{
"title" : "CHUNKS_DV",
"dbObject" : "AIUSER.CHUNKS_DV",
"dbObjectType" : "dualityView",
"dbObjectProperties" :
[
"insert",
"update",
"delete",
"check"
],
"type" : "object",
"properties" :
{
"_id" :
{
"type" : "number",
"extendedType" : "number",
"dbAssigned" : true,
"dbFieldProperties" :
[
"check"
]
},
"_metadata" :
{
"etag" :
This example shows how GET_METADATA can provide both general object metadata and JSON schema information without requiring a separate DBMS_JSON_SCHEMA.DESCRIBE call.
For more details and additional examples, see the Oracle documentation.
Further Readings
- PL/SQL Packages and Types Reference DBMS_DEVELOPER
- DBMS_METADATA API in Utilities
- DBMS_METADATA in PL/SQL Packages and Types Reference
- Generating DDL for your Oracle objects in SQL Developer Web (Jeff Smith)
