Of course. Working with Oracle CLOBs from Java is a common but sometimes tricky task. A CLOB (Character Large Object) is designed to store large amounts of character data (up to several terabytes), making it ideal for text like XML, JSON, logs, or long descriptions.

Here’s a comprehensive guide covering the key aspects: connecting, reading, writing, and best practices.
Prerequisites: JDBC Driver
First, you need the Oracle JDBC driver (also known as the JDBC Thin Driver). The modern, recommended version is the ojdbc8.jar (for Java 8) or ojdbc11.jar (for Java 11+).
You can download it from the Oracle Maven Repository or directly from Oracle's website.
If you're using Maven, add this to your pom.xml:

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.9.0.0</version> <!-- Use the latest version -->
</dependency>
Connecting to the Oracle Database
Establishing a connection is the first step. It's best practice to use a try-with-resources block to ensure your connection, statement, and result set are always closed.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class OracleConnectionUtil {
private static final String DB_URL = "jdbc:oracle:thin:@your_host:your_port:your_service_name";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(DB_URL, USER, PASS);
}
}
Reading Data from a CLOB
There are two primary methods for reading a CLOB, depending on the size of the data and your needs.
Method 1: Using Clob.getCharacterStream() (Recommended for Large CLOBs)
This is the most efficient method for large CLOBs because it streams the data character by character without loading the entire object into memory.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Clob;
public class ReadClobExample {
public static void main(String[] args) {
String sql = "SELECT id, document_content FROM documents WHERE id = ?";
try (Connection conn = OracleConnectionUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 101); // The ID of the document to read
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
int id = rs.getInt("id");
Clob documentClob = rs.getClob("document_content");
if (documentClob != null) {
// Use getCharacterStream() to read the CLOB as a stream
try (Reader reader = documentClob.getCharacterStream();
BufferedReader br = new BufferedReader(reader)) {
StringBuilder content = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
content.append(line).append("\n");
}
System.out.println("Document ID: " + id);
System.out.println("Content:\n" + content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
System.out.println("No document found with ID 101.");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Method 2: Using Clob.getSubString() (Convenient for Small CLOBs)
This method is simpler but can cause memory issues if the CLOB is very large (e.g., hundreds of megabytes or gigabytes), as it loads the entire CLOB content into a single String.
// ... inside the if (rs.next()) block ...
Clob documentClob = rs.getClob("document_content");
if (documentClob != null) {
// Be cautious with this method for large CLOBs!
String content = documentClob.getSubString(1, (int) documentClob.length());
System.out.println("Content (from getSubString):\n" + content);
}
Writing Data to a CLOB
Writing to a CLOB is a bit more complex than reading. You cannot simply do pstmt.setString(...). You must use a PreparedStatement and a special trick involving the empty_clob() function.
The standard procedure is:
- Insert a row with an empty CLOB (
empty_clob()). - Immediately retrieve the CLOB locator for that new row.
- Use the locator to get a
java.sql.Clobobject. - Write your data to this
Clobobject. - Update the row with the now-populated CLOB.
import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Clob;
public class WriteClobExample {
public static void main(String[] args) {
String insertSql = "INSERT INTO documents (id, name, document_content) VALUES (doc_seq.NEXTVAL, 'My Report', EMPTY_CLOB())";
String selectSql = "SELECT document_content FROM documents WHERE id = ? FOR UPDATE"; // FOR UPDATE is crucial
String updateSql = "UPDATE documents SET document_content = ? WHERE id = ?";
String largeText = "This is the first line of my large document.\n"
+ "This is the second line, and it contains some important data.\n"
+ "This process can handle thousands of lines of text...\n"
+ "...and so on, for as long as needed.";
try (Connection conn = OracleConnectionUtil.getConnection()) {
conn.setAutoCommit(false); // Start transaction
// Step 1: Insert a row with an empty CLOB
try (PreparedStatement pstmt = conn.prepareStatement(insertSql, new String[]{"id"})) {
pstmt.executeUpdate();
// Get the generated ID (optional, but good practice)
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
int newId = rs.getInt(1);
System.out.println("Inserted new document with ID: " + newId);
// Step 2: Retrieve the CLOB locator for the new row
try (PreparedStatement selectPstmt = conn.prepareStatement(selectSql)) {
selectPstmt.setInt(1, newId);
ResultSet clobRs = selectPstmt.executeQuery();
if (clobRs.next()) {
// Step 3: Get the Clob object
Clob documentClob = clobRs.getClob("document_content");
if (documentClob != null) {
// Step 4: Write data to the Clob object
try (Writer writer = documentClob.setCharacterStream(1)) {
writer.write(largeText);
} catch (IOException e) {
conn.rollback();
e.printStackTrace();
return;
}
}
}
}
// Step 5: Update the row (this step is often implicit in some drivers,
// but it's good to be explicit. The write above usually locks the row).
// In many cases, the commit is all that's needed.
}
}
}
// Commit the transaction
conn.commit();
System.out.println("CLOB successfully written and committed.");
} catch (SQLException e) {
e.printStackTrace();
try {
if (conn != null) conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
Note: The FOR UPDATE clause in the SELECT statement is essential. It locks the row, preventing other transactions from modifying it while you are writing the CLOB data.
Updating Data in a CLOB
Updating is very similar to writing. You use the same setCharacterStream method on the Clob object you retrieve from the SELECT ... FOR UPDATE statement.
// Assume you have an existing document ID
int documentIdToUpdate = 101;
String newContent = "This is the updated content for the document.";
String selectSql = "SELECT document_content FROM documents WHERE id = ? FOR UPDATE";
String updateSql = "UPDATE documents SET document_content = ? WHERE id = ?"; // May not be needed depending on driver
try (Connection conn = OracleConnectionUtil.getConnection()) {
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(selectSql)) {
pstmt.setInt(1, documentIdToUpdate);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
Clob clob = rs.getClob("document_content");
try (Writer writer = clob.setCharacterStream(1)) {
writer.write(newContent);
}
// The driver may automatically update the database when the writer is closed.
// If not, you might need an explicit UPDATE statement.
}
}
conn.commit();
System.out.println("CLOB updated successfully.");
} catch (SQLException | IOException e) {
e.printStackTrace();
// ... rollback logic ...
}
Best Practices and Key Considerations
- Resource Management: Always use
try-with-resourcesforConnection,Statement,ResultSet,Reader, andWriter. This prevents resource leaks. - Transactions: Wrap CLOB operations in a transaction (
conn.setAutoCommit(false)andconn.commit()). This ensures data integrity, especially for the multi-step write process. - Memory Usage: For large CLOBs, always use
getCharacterStream()andsetCharacterStream(). AvoidgetSubString()andsetString()as they can causeOutOfMemoryError. FOR UPDATE: When you need to read a CLOB for the purpose of updating it, always useSELECT ... FOR UPDATE. This locks the row and prevents other sessions from changing it while you're writing.- Connection Pooling: In a real application, never create a new connection for every request. Use a connection pool (like HikariCP, Apache DBCP) to manage database connections efficiently.
NCHAR/NCLOB: If your database uses national character sets (e.g.,AL32UTF8), you might encounterNCLOB. The usage is identical toCLOB, just replaceClobwithNClob. Most of the time,Clobis what you need.
