Micronaut 5.2 Oracle AI Database Integration

In June, Micronaut 5 unlocked advanced Oracle AI Database features: Oracle Spatial, AI Vector Search, JSON Relational Duality Views, transaction priority and Oracle session metadata, all through the Micronaut Data repository model.

Two minor releases have shipped since: Micronaut 5.1 in July and Micronaut 5.2 in September. They move up the stack, from mapping Oracle types to handling the hard parts of running production workloads on Oracle AI Database: commit outcomes, long-running transactions, high-concurrency updates and AI application state.

Here’s what’s new for developers using Oracle AI Database.

Deterministic commit outcomes with Oracle transaction recovery

The network drops right after your application sends COMMIT. Did it commit? Retrying blindly risks a double write. Failing risks losing the order.

Micronaut Data 5.2 adds @OracleTransactional.Recoverable, built on Oracle Transaction Guard. Micronaut captures the logical transaction id before commit. If the commit acknowledgement is lost, it opens a fresh connection and asks Oracle for the outcome. COMMITTED returns the original result. NOT_COMMITTED retries on a fresh connection.

@Transactional
@OracleTransactional.Recoverable(maxAttempts = 2, backoff = 200)
void placeOrder(Order order) {
    orderRepository.save(order);
    inventoryRepository.decrementStock(order.sku(), order.quantity());
}

Enable it per datasource:

datasources:
  default:
    dialect: ORACLE
    enable-oracle-transaction-recovery: true

On the database side, connect through a dedicated service with COMMIT_OUTCOME=TRUE, set a RETENTION_TIMEOUT, and grant EXECUTE ON DBMS_APP_CONT to the application user.

The feature applies to synchronous JDBC transactions that own the commit boundary. Do not stack it on Application Continuity or Transparent Application Failover (TAF), which already own the replay.

See also the Micronaut Data guide to Oracle lock-free reservations.

Workflows that outlive a request: sessionless transactions

Hold a seat now, ticket it in a later call. Oracle sessionless transactions let a JDBC transaction be suspended and resumed, potentially from another request. Oracle’s JDBC Developer’s Guide covers sessionless transactions. Micronaut Data 5.2 exposes this with two modes in @OracleTransactional:

@Singleton
public class BookingService {

    private final SeatRepository seatRepository;

    BookingService(SeatRepository seatRepository) {
        this.seatRepository = seatRepository;
    }

    @OracleTransactional(sessionless = OracleTransactional.Sessionless.SUSPEND, timeout = 60)
    public Long holdSeat(Seat seat) {
        return seatRepository.save(seat).getId();
    }

    @OracleTransactional(sessionless = OracleTransactional.Sessionless.REQUIRES_SUSPENDED)
    public void ticketSeat(Long id) {
        Seat seat = seatRepository.findById(id).orElseThrow();
        seat.setStatus("TICKETED");
        seatRepository.update(seat);
    }
}

SUSPEND starts the Oracle transaction, runs the work and detaches instead of committing. REQUIRES_SUSPENDED resumes it and commits. Inject OracleSessionlessTransactionPropagationOperations from io.micronaut.transaction.jdbc.oracle as propagation to export and import the transaction id. The SuspendedSeat record carries the seat id and transaction id between requests:

record SuspendedSeat(Long seatId, String transactionId) {}

SuspendedSeat held = propagation.withPropagation(() -> {
    Long seatId = bookingService.holdSeat(new Seat("JU502", "3a"));
    String txId = propagation.currentTransactionId().orElseThrow();
    return new SuspendedSeat(seatId, txId);
});

// later, in another request or a message consumer
propagation.withPropagation(held.transactionId(), () -> {
    bookingService.ticketSeat(held.seatId());
    return null;
});

Sessionless modes are for top-level boundaries only, and cannot be combined with @OracleTransactional.Recoverable.

For a step-by-step tutorial on sessionless transactions, see the Micronaut Data Oracle sessionless transactions tutorial, which uses Kafka in its example.

High-concurrency counters without locks: reservable columns

Inventory, balances and quotas are hot rows. Oracle AI Database supports lock-free reservations, which let transactions update reservable numeric columns by delta without row locks. See Oracle’s Lock-Free Reservation documentation. Micronaut Data 5.2 maps reservable columns with @Reservable. See the Micronaut Data guide to Oracle lock-free reservations:

@MappedEntity("account")
public record Account(
    @Id @GeneratedValue @Nullable Long id,
    String name,
    @Reservable @PositiveOrZero Long balance) {
}

Schema generation emits the RESERVABLE modifier and turns the validation annotation into a CHECK constraint:

CREATE TABLE "ACCOUNT" (
    "ID" NUMBER(19) NOT NULL,
    "NAME" VARCHAR(255) NOT NULL,
    "BALANCE" NUMBER(19) RESERVABLE
        CONSTRAINT "CK_ACCOUNT_BALANCE_GE_0" CHECK ("BALANCE" >= 0) NOT NULL,
    PRIMARY KEY("ID")
)

Reservations must be delta updates keyed by primary key. Derived repository methods starting with reserve generate them:

@JdbcRepository(dialect = Dialect.ORACLE)
public interface AccountRepository extends CrudRepository<Account, Long> {
    int reserveIncrementBalance(@Id Long id, Long balance);
    int reserveDecrementBalance(@Id Long id, Long balance);
}

Generated update operations skip reservable properties, so a concurrent name change never touches the balance. On insert, the initial balance value is still written. A failed CHECK surfaces as DataIntegrityViolationException. The feature works with JDBC and R2DBC.

Native BOOLEAN mapping

Micronaut Data can map Java boolean properties to Oracle’s native BOOLEAN type and generate boolean predicates in SQL. Set a build-time dialect target to enable the mapping:

@JdbcRepository(dialect = Dialect.ORACLE, version = "23.1")
interface ProductRepository extends CrudRepository<Product, Long> {
    List<Product> findByActiveTrue();
}

Micronaut Data then generates BOOLEAN, TRUE, FALSE, IS TRUE and IS FALSE. For schema generation, set datasources.default.dialect-options.version: 23.1 as well. See the Micronaut Data Oracle BOOLEAN guide for a complete example.

Upserts

An upsert inserts a row if no row matches the specified conflict target, or updates the matching row if one does. Micronaut Data 5.2 adds upserts for SQL repositories. A method named upsert or upsertAll uses the entity id as its conflict target; that convention is suitable when the id is assigned by the application. For a generated-id entity with a unique email, use @Upsert with a custom conflict target and an upsert-prefixed method:

@JdbcRepository(dialect = Dialect.ORACLE)
interface CustomerRepository extends CrudRepository<Customer, Long> {

    @Upsert(conflictsOn = "email")
    void upsertByEmail(Customer customer);
}

Oracle generates a MERGE matching the email for this method. See the Micronaut Data Oracle upsert guide for a complete example.

Transaction priority rollbacks are now visible

Micronaut 5.0 introduced @OracleTransactional(priority = ...). When Oracle rolls back a lower-priority transaction to unblock a higher-priority one, Micronaut Data 5.2 now throws a dedicated exception:

try {
    checkoutService.checkout(accountId);
} catch (OracleTransactionPriorityException e) {
    // Already rolled back by Oracle (ORA-63300). Re-read and retry.
}

The AI stack on one database: chat memory, agents and RAG

Micronaut LangChain4j already offered an Oracle embedding store for AI Vector Search, as well as OCI Generative AI chat and embedding models. Micronaut 5.1 adds auto-configuration for the Oracle chat memory store, so conversation history lives in the same database as your vectors and your business data.

datasources:
  default:
    dialect: oracle

langchain4j:
  oracle:
    embedding-stores:
      default:
        table:
          name: DOCUMENT_EMBEDDINGS
          create-option: create_if_not_exists
  chat-memory-store:
    oracle:
      default:
        enabled: true
        table-name: CHAT_MEMORY
        memory-id-column-name: MEMORY_ID
        content-column-name: CONTENT

Add io.micronaut.langchain4j:micronaut-langchain4j-store-oracleand a JDBC pool such as micronaut-jdbc-ucp. The chat memory table must exist; the default schema is CHAT_MEMORY(MEMORY_ID, CONTENT).

Oracle UCP metrics in OpenTelemetry

Micronaut 5.1 adds a dedicated module that publishes UCP connection pool metrics to OpenTelemetry.

Smaller change

Micronaut SQL 7.2 updates the Oracle JDBC driver to 23.26.3.0.0.

Summary

Micronaut 5.0 made Oracle AI Database’s vector, spatial and JSON capabilities feel like ordinary repository methods. Micronaut 5.1 and 5.2 take on the operational side: commits that resolve deterministically, transactions that span requests, counters that never block, and AI conversation state that lives next to the data it reasons about. All of it stays declarative, compile-time checked, and native-image ready with GraalVM.

Get the most out of Oracle AI Database with Micronaut.