Oracle Type Support
Foundations JDBC provides comprehensive support for Oracle data types, including OBJECT types, nested tables, intervals, and LOB types.
Numeric Types
Universal NUMBER Type
| Oracle Type | Java Type | Notes |
|---|---|---|
NUMBER | BigDecimal | Arbitrary precision |
NUMBER(p,0) where p <= 9 | Integer | 32-bit integer |
NUMBER(p,0) where 9 < p <= 18 | Long | 64-bit integer |
NUMBER(p,s) | BigDecimal | Fixed precision/scale |
- Kotlin
- Java
- Scala
val numberType: OracleType<BigDecimal> = OracleTypes.number
val decimal: OracleType<BigDecimal> = OracleTypes.number(10, 2) // NUMBER(10,2)
val intType: OracleType<Int> = OracleTypes.numberAsInt(9) // NUMBER(9)
val longType: OracleType<Long> = OracleTypes.numberAsLong(18) // NUMBER(18)
OracleType<BigDecimal> numberType = OracleTypes.number;
OracleType<BigDecimal> decimal = OracleTypes.number(10, 2); // NUMBER(10,2)
OracleType<Integer> intType = OracleTypes.numberAsInt(9); // NUMBER(9)
OracleType<Long> longType = OracleTypes.numberAsLong(18); // NUMBER(18)
val numberType: OracleType[BigDecimal] = OracleTypes.number
val decimal: OracleType[BigDecimal] = OracleTypes.number(10, 2) // NUMBER(10,2)
val intType: OracleType[Int] = OracleTypes.numberAsInt(9) // NUMBER(9)
val longType: OracleType[Long] = OracleTypes.numberAsLong(18) // NUMBER(18)
IEEE 754 Floating Point
| Oracle Type | Java Type | Notes |
|---|---|---|
BINARY_FLOAT | Float | 32-bit IEEE 754 |
BINARY_DOUBLE | Double | 64-bit IEEE 754 |
FLOAT(p) | Double | Maps to NUMBER internally |
- Kotlin
- Java
- Scala
val binaryFloat: OracleType<Float> = OracleTypes.binaryFloat
val binaryDouble: OracleType<Double> = OracleTypes.binaryDouble
val floatType: OracleType<Double> = OracleTypes.float_(126) // FLOAT(126)
OracleType<Float> binaryFloat = OracleTypes.binaryFloat;
OracleType<Double> binaryDouble = OracleTypes.binaryDouble;
OracleType<Double> floatType = OracleTypes.float_(126); // FLOAT(126)
val binaryFloat: OracleType[Float] = OracleTypes.binaryFloat
val binaryDouble: OracleType[Double] = OracleTypes.binaryDouble
val floatType: OracleType[Double] = OracleTypes.float_(126) // FLOAT(126)
Boolean Type
| Oracle Type | Java Type | Notes |
|---|---|---|
BOOLEAN | Boolean | Oracle 23c+ native |
NUMBER(1) | Boolean | Traditional 0/1 convention |
- Kotlin
- Java
- Scala
val boolNative: OracleType<Boolean> = OracleTypes.boolean_ // Oracle 23c+
val boolNumber: OracleType<Boolean> = OracleTypes.numberAsBoolean // NUMBER(1)
OracleType<Boolean> boolNative = OracleTypes.boolean_; // Oracle 23c+
OracleType<Boolean> boolNumber = OracleTypes.numberAsBoolean; // NUMBER(1)
val boolNative: OracleType[Boolean] = OracleTypes.boolean_ // Oracle 23c+
val boolNumber: OracleType[Boolean] = OracleTypes.numberAsBoolean // NUMBER(1)
Character Types
| Oracle Type | Java Type | Max Length | Notes |
|---|---|---|---|
VARCHAR2(n) | String | 4000 bytes | Variable-length |
CHAR(n) | String | 2000 bytes | Fixed-length, blank-padded |
NVARCHAR2(n) | String | 4000 bytes | National character set |
NCHAR(n) | String | 2000 bytes | National fixed-length |
LONG | String | 2 GB | Deprecated, use CLOB |
- Kotlin
- Java
- Scala
val varcharType: OracleType<String> = OracleTypes.varchar2
val varchar100: OracleType<String> = OracleTypes.varchar2(100)
val charType: OracleType<String> = OracleTypes.char_(10)
val nvarcharType: OracleType<String> = OracleTypes.nvarchar2(100)
OracleType<String> varcharType = OracleTypes.varchar2;
OracleType<String> varchar100 = OracleTypes.varchar2(100);
OracleType<String> charType = OracleTypes.char_(10);
OracleType<String> nvarcharType = OracleTypes.nvarchar2(100);
val varcharType: OracleType[String] = OracleTypes.varchar2
val varchar100: OracleType[String] = OracleTypes.varchar2(100)
val charType: OracleType[String] = OracleTypes.char_(10)
val nvarcharType: OracleType[String] = OracleTypes.nvarchar2(100)
Non-Empty String Variants
For NOT NULL columns, use NonEmptyString to guarantee non-empty values:
- Kotlin
- Java
- Scala
val nonEmpty: OracleType<NonEmptyString> = OracleTypes.varchar2NonEmpty(100)
val nvarNonEmpty: OracleType<NonEmptyString> = OracleTypes.nvarchar2NonEmpty(100)
OracleType<NonEmptyString> nonEmpty = OracleTypes.varchar2NonEmpty(100);
OracleType<NonEmptyString> nvarNonEmpty = OracleTypes.nvarchar2NonEmpty(100);
val nonEmpty: OracleType[NonEmptyString] = OracleTypes.varchar2NonEmpty(100)
val nvarNonEmpty: OracleType[NonEmptyString] = OracleTypes.nvarchar2NonEmpty(100)
Padded String for CHAR
For CHAR columns preserving padding:
- Kotlin
- Java
- Scala
val padded: OracleType<PaddedString> = OracleTypes.charPadded(10) // CHAR(10)
val npadded: OracleType<PaddedString> = OracleTypes.ncharPadded(10) // NCHAR(10)
OracleType<PaddedString> padded = OracleTypes.charPadded(10); // CHAR(10)
OracleType<PaddedString> npadded = OracleTypes.ncharPadded(10); // NCHAR(10)
val padded: OracleType[PaddedString] = OracleTypes.charPadded(10) // CHAR(10)
val npadded: OracleType[PaddedString] = OracleTypes.ncharPadded(10) // NCHAR(10)
Large Object (LOB) Types
| Oracle Type | Java Type | Max Size | Notes |
|---|---|---|---|
CLOB | String | 4 GB | Character LOB |
NCLOB | String | 4 GB | National character LOB |
BLOB | byte[] | 4 GB | Binary LOB |
- Kotlin
- Java
- Scala
val clobType: OracleType<String> = OracleTypes.clob
val nclobType: OracleType<String> = OracleTypes.nclob
val blobType: OracleType<ByteArray> = OracleTypes.blob
// Non-empty variants
val clobNonEmpty: OracleType<NonEmptyString> = OracleTypes.clobNonEmpty
val blobNonEmpty: OracleType<NonEmptyBlob> = OracleTypes.blobNonEmpty
OracleType<String> clobType = OracleTypes.clob;
OracleType<String> nclobType = OracleTypes.nclob;
OracleType<byte[]> blobType = OracleTypes.blob;
// Non-empty variants
OracleType<NonEmptyString> clobNonEmpty = OracleTypes.clobNonEmpty;
OracleType<NonEmptyBlob> blobNonEmpty = OracleTypes.blobNonEmpty;
val clobType: OracleType[String] = OracleTypes.clob
val nclobType: OracleType[String] = OracleTypes.nclob
val blobType: OracleType[Array[Byte]] = OracleTypes.blob
// Non-empty variants
val clobNonEmpty: OracleType[NonEmptyString] = OracleTypes.clobNonEmpty
val blobNonEmpty: OracleType[NonEmptyBlob] = OracleTypes.blobNonEmpty
Binary Types
| Oracle Type | Java Type | Max Length | Notes |
|---|---|---|---|
RAW(n) | byte[] | 2000 bytes | Variable-length binary |
LONG RAW | byte[] | 2 GB | Deprecated, use BLOB |
- Kotlin
- Java
- Scala
val rawType: OracleType<ByteArray> = OracleTypes.raw
val raw100: OracleType<ByteArray> = OracleTypes.raw(100) // RAW(100)
// Non-empty variant
val rawNonEmpty: OracleType<NonEmptyBlob> = OracleTypes.rawNonEmpty(100)
OracleType<byte[]> rawType = OracleTypes.raw;
OracleType<byte[]> raw100 = OracleTypes.raw(100); // RAW(100)
// Non-empty variant
OracleType<NonEmptyBlob> rawNonEmpty = OracleTypes.rawNonEmpty(100);
val rawType: OracleType[Array[Byte]] = OracleTypes.raw
val raw100: OracleType[Array[Byte]] = OracleTypes.raw(100) // RAW(100)
// Non-empty variant
val rawNonEmpty: OracleType[NonEmptyBlob] = OracleTypes.rawNonEmpty(100)
Date/Time Types
| Oracle Type | Java Type | Notes |
|---|---|---|
DATE | LocalDateTime | Date + time (second precision) |
TIMESTAMP | LocalDateTime | Fractional seconds (default: 6) |
TIMESTAMP WITH TIME ZONE | OffsetDateTime | Explicit timezone |
TIMESTAMP WITH LOCAL TIME ZONE | Instant | Session timezone |
- Kotlin
- Java
- Scala
val dateType: OracleType<LocalDateTime> = OracleTypes.date
val tsType: OracleType<LocalDateTime> = OracleTypes.timestamp
val ts3: OracleType<LocalDateTime> = OracleTypes.timestamp(3) // TIMESTAMP(3)
val tstz: OracleType<OffsetDateTime> = OracleTypes.timestampWithTimeZone
val tsltz: OracleType<Instant> = OracleTypes.timestampWithLocalTimeZone
OracleType<LocalDateTime> dateType = OracleTypes.date;
OracleType<LocalDateTime> tsType = OracleTypes.timestamp;
OracleType<LocalDateTime> ts3 = OracleTypes.timestamp(3); // TIMESTAMP(3)
OracleType<OffsetDateTime> tstz = OracleTypes.timestampWithTimeZone;
OracleType<Instant> tsltz = OracleTypes.timestampWithLocalTimeZone;
val dateType: OracleType[LocalDateTime] = OracleTypes.date
val tsType: OracleType[LocalDateTime] = OracleTypes.timestamp
val ts3: OracleType[LocalDateTime] = OracleTypes.timestamp(3) // TIMESTAMP(3)
val tstz: OracleType[OffsetDateTime] = OracleTypes.timestampWithTimeZone
val tsltz: OracleType[Instant] = OracleTypes.timestampWithLocalTimeZone
Note: Oracle DATE includes time (unlike SQL standard), so it maps to LocalDateTime, not LocalDate.
Interval Types
| Oracle Type | Java Type | Notes |
|---|---|---|
INTERVAL YEAR TO MONTH | OracleIntervalYM | Years and months |
INTERVAL DAY TO SECOND | OracleIntervalDS | Days, hours, minutes, seconds |
- Kotlin
- Java
- Scala
val ymType: OracleType<OracleIntervalYM> = OracleTypes.intervalYearToMonth
val ym4: OracleType<OracleIntervalYM> = OracleTypes.intervalYearToMonth(4)
val dsType: OracleType<OracleIntervalDS> = OracleTypes.intervalDayToSecond
val ds96: OracleType<OracleIntervalDS> = OracleTypes.intervalDayToSecond(9, 6)
// Create and use intervals
val interval: OracleIntervalYM = OracleIntervalYM.parse("+02-05") // 2 years, 5 months
val oracle: String = interval.toOracleFormat() // "+02-05"
val iso: String = interval.toIso8601() // "P2Y5M"
OracleType<OracleIntervalYM> ymType = OracleTypes.intervalYearToMonth;
OracleType<OracleIntervalYM> ym4 = OracleTypes.intervalYearToMonth(4);
OracleType<OracleIntervalDS> dsType = OracleTypes.intervalDayToSecond;
OracleType<OracleIntervalDS> ds96 = OracleTypes.intervalDayToSecond(9, 6);
// Create and use intervals
OracleIntervalYM interval = OracleIntervalYM.parse("+02-05"); // 2 years, 5 months
String oracle = interval.toOracleFormat(); // "+02-05"
String iso = interval.toIso8601(); // "P2Y5M"
val ymType: OracleType[OracleIntervalYM] = OracleTypes.intervalYearToMonth
val ym4: OracleType[OracleIntervalYM] = OracleTypes.intervalYearToMonth(4)
val dsType: OracleType[OracleIntervalDS] = OracleTypes.intervalDayToSecond
val ds96: OracleType[OracleIntervalDS] = OracleTypes.intervalDayToSecond(9, 6)
// Create and use intervals
val interval: OracleIntervalYM = OracleIntervalYM.parse("+02-05") // 2 years, 5 months
val oracle: String = interval.toOracleFormat() // "+02-05"
val iso: String = interval.toIso8601() // "P2Y5M"
ROWID Types
| Oracle Type | Java Type | Notes |
|---|---|---|
ROWID | String | Physical row address (18 chars) |
UROWID | String | Universal ROWID (max 4000 bytes) |
- Kotlin
- Java
- Scala
val rowidType: OracleType<String> = OracleTypes.rowId
val urowidType: OracleType<String> = OracleTypes.uRowId
val urowid1000: OracleType<String> = OracleTypes.uRowId(1000)
OracleType<String> rowidType = OracleTypes.rowId;
OracleType<String> urowidType = OracleTypes.uRowId;
OracleType<String> urowid1000 = OracleTypes.uRowId(1000);
val rowidType: OracleType[String] = OracleTypes.rowId
val urowidType: OracleType[String] = OracleTypes.uRowId
val urowid1000: OracleType[String] = OracleTypes.uRowId(1000)
XML and JSON Types
| Oracle Type | Java Type | Notes |
|---|---|---|
XMLTYPE | String | XML document storage |
JSON | Json | Native JSON (Oracle 21c+) |
- Kotlin
- Java
- Scala
val xmlType: OracleType<String> = OracleTypes.xmlType
val jsonType: OracleType<Json> = OracleTypes.json
val data: Json = Json("{\"name\": \"Oracle\"}")
OracleType<String> xmlType = OracleTypes.xmlType;
OracleType<Json> jsonType = OracleTypes.json;
Json data = new Json("{\"name\": \"Oracle\"}");
val xmlType: OracleType[String] = OracleTypes.xmlType
val jsonType: OracleType[Json] = OracleTypes.json
val data: Json = new Json("{\"name\": \"Oracle\"}")
Object Types
Oracle OBJECT types are built from a RowCodecNamed via compositeOf:
OracleType<Address> addressType = OracleTypes.compositeOf("ADDRESS_T",
RowCodec.<Address>namedBuilder()
.field("STREET", OracleTypes.varchar2(200), Address::street)
.field("CITY", OracleTypes.varchar2(100), Address::city)
.field("ZIP", OracleTypes.varchar2(10), Address::zip)
.build(Address::new));
The returned OracleType can be used with OracleVArray and OracleNestedTable for collection columns.
VARRAYs
VARRAYs are fixed-maximum-size ordered collections (CREATE TYPE ... AS VARRAY(n) OF ...).
Mapped to List<T> in Java. The max size is enforced on write.
- Kotlin
- Java
- Scala
// CREATE TYPE phone_list AS VARRAY(5) OF VARCHAR2(25);
val phoneList: OracleType<List<String>> =
OracleVArray.of("PHONE_LIST", 5, OracleTypes.varchar2(25))
// CREATE TYPE score_array AS VARRAY(100) OF NUMBER;
val scores: OracleType<List<BigDecimal>> =
OracleVArray.of("SCORE_ARRAY", 100, OracleTypes.number)
// CREATE TYPE phone_list AS VARRAY(5) OF VARCHAR2(25);
static final OracleType<List<String>> phoneList =
OracleVArray.of("PHONE_LIST", 5, OracleTypes.varchar2(25));
// CREATE TYPE score_array AS VARRAY(100) OF NUMBER;
static final OracleType<List<java.math.BigDecimal>> scores =
OracleVArray.of("SCORE_ARRAY", 100, OracleTypes.number);
// CREATE TYPE phone_list AS VARRAY(5) OF VARCHAR2(25);
val phoneList: OracleType[java.util.List[String]] =
OracleVArray.of("PHONE_LIST", 5, OracleTypes.varchar2(25))
// CREATE TYPE score_array AS VARRAY(100) OF NUMBER;
val scores: OracleType[java.util.List[java.math.BigDecimal]] =
OracleVArray.of("SCORE_ARRAY", 100, OracleTypes.number)
Nested Tables
Nested tables are unbounded collections (CREATE TYPE ... AS TABLE OF ...).
Like VARRAYs, they map to List<T> but have no size limit.
Nested tables can hold OBJECT types for complex hierarchical data.
- Kotlin
- Java
- Scala
// CREATE TYPE order_item_t AS OBJECT (
// product_name VARCHAR2(100),
// quantity NUMBER(10),
// unit_price NUMBER(12,2)
// );
data class OrderItem(val productName: String, val quantity: Int, val unitPrice: BigDecimal)
val orderItemType: OracleType<OrderItem> =
OracleTypes.compositeOf(
"ORDER_ITEM_T",
RowCodec.namedBuilder<OrderItem>()
.field("PRODUCT_NAME", OracleTypes.varchar2(100), OrderItem::productName)
.field("QUANTITY", OracleTypes.numberAsInt(10), OrderItem::quantity)
.field("UNIT_PRICE", OracleTypes.number(12, 2), OrderItem::unitPrice)
.build(::OrderItem))
// CREATE TYPE order_items_t AS TABLE OF order_item_t;
val orderItems: OracleType<List<OrderItem>> =
OracleNestedTable.of("ORDER_ITEMS_T", orderItemType)
// CREATE TYPE order_item_t AS OBJECT (
// product_name VARCHAR2(100),
// quantity NUMBER(10),
// unit_price NUMBER(12,2)
// );
record OrderItem(String productName, int quantity, BigDecimal unitPrice) {}
static final OracleType<OrderItem> orderItemType =
OracleTypes.compositeOf(
"ORDER_ITEM_T",
RowCodec.<OrderItem>namedBuilder()
.field("PRODUCT_NAME", OracleTypes.varchar2(100), OrderItem::productName)
.field("QUANTITY", OracleTypes.numberAsInt(10), OrderItem::quantity)
.field("UNIT_PRICE", OracleTypes.number(12, 2), OrderItem::unitPrice)
.build(OrderItem::new));
// CREATE TYPE order_items_t AS TABLE OF order_item_t;
static final OracleType<List<OrderItem>> orderItems =
OracleNestedTable.of("ORDER_ITEMS_T", orderItemType);
// CREATE TYPE order_item_t AS OBJECT (
// product_name VARCHAR2(100),
// quantity NUMBER(10),
// unit_price NUMBER(12,2)
// );
case class OrderItem(productName: String, quantity: Integer, unitPrice: java.math.BigDecimal)
val orderItemType: OracleType[OrderItem] =
OracleTypes.compositeOf(
"ORDER_ITEM_T",
RowCodec
.namedBuilder[OrderItem]()
.field("PRODUCT_NAME", OracleTypes.varchar2(100), (o: OrderItem) => o.productName)
.field("QUANTITY", OracleTypes.numberAsInt(10), (o: OrderItem) => o.quantity)
.field("UNIT_PRICE", OracleTypes.number(12, 2), (o: OrderItem) => o.unitPrice)
.build((name: String, qty: Integer, price: java.math.BigDecimal) => OrderItem(name, qty, price)))
// CREATE TYPE order_items_t AS TABLE OF order_item_t;
val orderItems: OracleType[java.util.List[OrderItem]] =
OracleNestedTable.of("ORDER_ITEMS_T", orderItemType)
Nullable Types
Any type can be made nullable using .opt():
- Kotlin
- Java
- Scala
val notNull: OracleType<Int> = OracleTypes.numberInt
val nullable: OracleType<Int?> = OracleTypes.numberInt.opt()
OracleType<Integer> notNull = OracleTypes.numberInt;
OracleType<Optional<Integer>> nullable = OracleTypes.numberInt.opt();
val notNull: OracleType[Int] = OracleTypes.numberInt
val nullable: OracleType[Option[Int]] = OracleTypes.numberInt.opt
Oracle Nullability Behavior
Oracle treats empty strings as NULL — INSERT INTO t (col) VALUES ('') stores NULL.
This means VARCHAR2 columns are effectively always nullable from Oracle's perspective,
even if the column has a NOT NULL constraint (an empty string insert will fail with a
constraint violation, not store an empty string).
When using query analysis, Oracle may report all VARCHAR2/CHAR columns as nullable.
Use .nullableOk() on the type if you want to suppress nullability warnings for columns
you know are NOT NULL in the schema:
OracleType<String> name = OracleTypes.varchar2(100).nullableOk();
Custom Domain Types
Wrap base types with custom Java types using transform:
- Kotlin
- Java
- Scala
// Wrapper type
data class EmployeeId(val value: Long)
// Create OracleType from NUMBER
val empIdType: OracleType<EmployeeId> =
OracleTypes.numberLong.transform(::EmployeeId, EmployeeId::value)
// Wrapper type
public record EmployeeId(Long value) {}
// Create OracleType from NUMBER
OracleType<EmployeeId> empIdType =
OracleTypes.numberLong.transform(EmployeeId::new, EmployeeId::value);
// Wrapper type
case class EmployeeId(value: Long)
// Create OracleType from NUMBER
val empIdType: OracleType[EmployeeId] =
OracleTypes.numberLong.transform(EmployeeId.apply, _.value)