or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

channel-message-store.mdinbound-polling.mdindex.mdjava-dsl.mdlock-registry.mdmessage-store.mdmetadata-store.mdoutbound-gateway.mdoutbound-updates.mdparameter-factories.mdpostgres-channels.mdstored-procedures.md
tile.json

outbound-updates.mddocs/

Outbound Updates

Outbound update capabilities enable applications to execute SQL INSERT, UPDATE, or DELETE statements from incoming Spring Integration messages. This is useful for persisting message data to databases, logging, audit trails, and fire-and-forget database operations.

Key Information for Agents

Required Dependencies:

  • spring-integration-jdbc (this package)
  • spring-integration-core is required
  • DataSource or JdbcOperations bean must be configured

Default Behaviors:

  • keysGenerated=false (auto-generated keys not retrieved)
  • usePayloadAsParameterSource=true (only payload used for parameters, not headers)
  • Default parameter factory: BeanPropertySqlParameterSourceFactory
  • Batch operations supported when payload is Iterable/Collection
  • Batch operations NOT compatible with keysGenerated=true
  • Fire-and-forget pattern (no reply message)

Threading Model:

  • Executes on message handling thread
  • Can be configured with async channels for non-blocking operations
  • Thread-safe when using JdbcOperations (connection pooling)

Lifecycle:

  • Managed by Spring Integration message handler lifecycle
  • No special startup/shutdown required

Exceptions:

  • DataAccessException - Database access failures
  • MessagingException - Message handling failures
  • IllegalArgumentException - Invalid configuration

Edge Cases:

  • Batch updates NOT supported when keysGenerated=true (throws exception)
  • When payload is Iterable, executes batch update (one PreparedStatement per element)
  • When payload is not Iterable, executes single update
  • usePayloadAsParameterSource=false allows access to message headers in parameter expressions
  • Parameter names are case-sensitive (must match SQL exactly)
  • Static parameters can be merged with dynamic parameters via parameter factory
  • PreparedStatementSetter provides low-level control (alternative to parameter factory)

Core Classes

JdbcMessageHandler

package org.springframework.integration.jdbc.outbound;

public class JdbcMessageHandler extends AbstractMessageHandler {
    public JdbcMessageHandler(DataSource dataSource, String updateSql);
    public JdbcMessageHandler(JdbcOperations jdbcOperations, String updateSql);

    public void setKeysGenerated(boolean keysGenerated);
    public void setSqlParameterSourceFactory(SqlParameterSourceFactory factory);
    public void setUsePayloadAsParameterSource(boolean usePayloadAsParameterSource);
    public void setPreparedStatementSetter(@Nullable MessagePreparedStatementSetter setter);
    public String getComponentType();
}

StoredProcMessageHandler

package org.springframework.integration.jdbc.outbound;

public class StoredProcMessageHandler extends AbstractMessageHandler {
    public StoredProcMessageHandler(StoredProcExecutor storedProcExecutor);
}

Usage Examples

Basic INSERT

import org.springframework.integration.jdbc.outbound.JdbcMessageHandler;
import org.springframework.integration.support.MessageBuilder;

// Basic INSERT using message payload
JdbcMessageHandler insertHandler = new JdbcMessageHandler(
    dataSource,
    "INSERT INTO orders (order_number, amount, status) VALUES (:orderNumber, :amount, :status)"
);
insertHandler.setUsePayloadAsParameterSource(true);

// Send message with payload as domain object
Order order = new Order("ORD-123", new BigDecimal("99.99"), "PENDING");
Message<Order> message = MessageBuilder.withPayload(order).build();
insertHandler.handleMessage(message);

UPDATE with Headers

// UPDATE using message payload and headers
JdbcMessageHandler updateHandler = new JdbcMessageHandler(
    dataSource,
    "UPDATE audit_log SET message = :payload, user_id = :headers[userId], timestamp = :headers[timestamp] WHERE id = :payload.id"
);
updateHandler.setUsePayloadAsParameterSource(false); // Access both payload and headers

Message<?> auditMessage = MessageBuilder
    .withPayload(new AuditEntry(123, "User logged in"))
    .setHeader("userId", "user-456")
    .setHeader("timestamp", System.currentTimeMillis())
    .build();
updateHandler.handleMessage(auditMessage);

Batch INSERT

// Batch INSERT for Iterable payload
JdbcMessageHandler batchHandler = new JdbcMessageHandler(
    dataSource,
    "INSERT INTO events (event_type, data) VALUES (:eventType, :data)"
);

List<Event> events = List.of(
    new Event("LOGIN", "User logged in"),
    new Event("LOGOUT", "User logged out"),
    new Event("PURCHASE", "User made purchase")
);
Message<List<Event>> batchMessage = MessageBuilder.withPayload(events).build();
batchHandler.handleMessage(batchMessage); // Executes batch insert

Expression-Based Parameters

import org.springframework.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory;

// Using custom SqlParameterSourceFactory with SpEL expressions
ExpressionEvaluatingSqlParameterSourceFactory factory =
    new ExpressionEvaluatingSqlParameterSourceFactory();

Map<String, String> expressions = Map.of(
    "orderId", "payload.id",
    "total", "payload.amount * payload.quantity",
    "userId", "headers['user']",
    "timestamp", "T(System).currentTimeMillis()"
);
factory.setParameterExpressions(expressions);

JdbcMessageHandler exprHandler = new JdbcMessageHandler(
    dataSource,
    "INSERT INTO order_summary (order_id, total, user_id, timestamp) VALUES (:orderId, :total, :userId, :timestamp)"
);
exprHandler.setSqlParameterSourceFactory(factory);

Low-Level PreparedStatement Control

import org.springframework.integration.jdbc.MessagePreparedStatementSetter;

// Using MessagePreparedStatementSetter for low-level control
JdbcMessageHandler psHandler = new JdbcMessageHandler(
    dataSource,
    "INSERT INTO events (id, type, data, created_at) VALUES (?, ?, ?, ?)"
);

psHandler.setPreparedStatementSetter((ps, message) -> {
    Event event = (Event) message.getPayload();
    ps.setLong(1, event.getId());
    ps.setString(2, event.getType());
    ps.setString(3, event.getData());
    ps.setTimestamp(4, new Timestamp(message.getHeaders().getTimestamp()));
});

Stored Procedure Handler

import org.springframework.integration.jdbc.StoredProcExecutor;
import org.springframework.integration.jdbc.outbound.StoredProcMessageHandler;
import org.springframework.integration.jdbc.storedproc.ProcedureParameter;

// Configure stored procedure executor
StoredProcExecutor executor = new StoredProcExecutor(dataSource);
executor.setStoredProcedureName("LOG_ORDER_EVENT");

// Define parameters from message
List<ProcedureParameter> params = List.of(
    new ProcedureParameter("order_id", null, "payload.orderId"),
    new ProcedureParameter("event_type", null, "payload.eventType"),
    new ProcedureParameter("user_id", null, "headers['userId']"),
    new ProcedureParameter("timestamp", null, "T(System).currentTimeMillis()")
);
executor.setProcedureParameters(params);

// Create handler (ignores any return values)
StoredProcMessageHandler handler = new StoredProcMessageHandler(executor);

// Send message
Message<?> message = MessageBuilder
    .withPayload(new OrderEvent(123, "CREATED"))
    .setHeader("userId", "user-789")
    .build();
handler.handleMessage(message);

Integration with Spring Integration

Message handlers integrate with Spring Integration channels and flows:

import org.springframework.integration.dsl.IntegrationFlow;
import static org.springframework.integration.jdbc.dsl.Jdbc.*;

@Bean
public IntegrationFlow jdbcOutboundFlow(DataSource dataSource) {
    return IntegrationFlow
        .from("inputChannel")
        .handle(outboundAdapter(dataSource,
                "INSERT INTO processed_orders (order_id, amount, processed_at) VALUES (:id, :amount, :processedAt)"))
        .get();
}

Or using XML configuration:

<int-jdbc:outbound-channel-adapter
    channel="inputChannel"
    data-source="dataSource"
    query="INSERT INTO orders (order_number, amount) VALUES (:payload.orderNumber, :payload.amount)"
    sql-parameter-source-factory="parameterFactory"/>

Deprecated Classes

Important Migration Notice:

The class org.springframework.integration.jdbc.JdbcMessageHandler is deprecated since version 7.0 and marked for removal.

This class has been moved to the org.springframework.integration.jdbc.outbound package. Users should update their imports:

Old (Deprecated):

import org.springframework.integration.jdbc.JdbcMessageHandler;

New (Current):

import org.springframework.integration.jdbc.outbound.JdbcMessageHandler;

The deprecated class is a simple wrapper that extends the new class, so migration only requires updating import statements.

Key Considerations

  • Fire-and-Forget: Handlers don't return results; use outbound gateway for request-reply
  • Transaction Management: Executes in existing transaction or creates new one if transaction manager configured
  • Batch Operations: When payload is Iterable/Collection, executes batch update (NOT compatible with keysGenerated=true)
  • Parameter Binding: Use :paramName syntax for named parameters
  • Payload vs Message: Set usePayloadAsParameterSource=false to access message headers
  • Error Handling: Exceptions propagate to error channel if configured
  • Generated Keys: Keys can be generated but not returned (fire-and-forget); use gateway for key retrieval
  • Performance: Batch operations are more efficient than individual updates
  • SQL Injection: Use parameter binding, never concatenate strings