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-gateway.mddocs/

Outbound Gateway

Outbound gateway capabilities enable request-reply database operations where applications send request messages, execute database updates and/or queries, and receive reply messages with results. This is useful for retrieving generated keys, fetching data after updates, and complex database interactions requiring response data.

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:

  • maxRows=1 (default limit for SELECT queries)
  • keysGenerated=false (auto-generated keys not retrieved by default)
  • Default row mapper: ColumnMapRowMapper (returns Map<String, Object>)
  • Returns single object when SELECT returns exactly 1 row and maxRows=1
  • Returns List<?> when SELECT returns multiple rows or maxRows>1
  • Returns Map<String, Object> with generated keys when no SELECT query and keysGenerated=true
  • UPDATE and SELECT execute in same transaction

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 (e.g., requiresReply=true and null result)
  • IllegalArgumentException - Invalid configuration (e.g., null maxRows)

Edge Cases:

  • maxRows must not be null (throws IllegalArgumentException if null)
  • Empty SELECT result returns empty list (not null)
  • Generated keys available to SELECT query via :GENERATED_KEY parameter
  • When keysGenerated=true and no SELECT query, reply payload is Map<String, Object> with keys
  • When SELECT query configured, reply payload is query results (single object or List)
  • expectSingleResult for stored procedures extracts single result from result map
  • Stored procedure result map contains result sets, OUT parameters, and return values
  • requiresReply=true throws MessagingException if reply is null or empty

Core Classes

JdbcOutboundGateway

package org.springframework.integration.jdbc.outbound;

public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler {
    public JdbcOutboundGateway(DataSource dataSource, String updateQuery);
    public JdbcOutboundGateway(DataSource dataSource, String updateQuery, String selectQuery);
    public JdbcOutboundGateway(JdbcOperations jdbcOperations, String updateQuery);
    public JdbcOutboundGateway(JdbcOperations jdbcOperations, String updateQuery, @Nullable String selectQuery);

    public void setMaxRows(Integer maxRows);
    public void setKeysGenerated(boolean keysGenerated);
    public void setRequestSqlParameterSourceFactory(SqlParameterSourceFactory factory);
    public void setRequestPreparedStatementSetter(MessagePreparedStatementSetter setter);
    public void setReplySqlParameterSourceFactory(SqlParameterSourceFactory factory);
    public void setRowMapper(RowMapper<?> rowMapper);
    public String getComponentType();
}

StoredProcOutboundGateway

package org.springframework.integration.jdbc.outbound;

public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHandler {
    public StoredProcOutboundGateway(StoredProcExecutor storedProcExecutor);

    public void setExpectSingleResult(boolean expectSingleResult);
    public void setRequiresReply(boolean requiresReply);
}

Usage Examples

INSERT and Retrieve Generated Keys

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

// INSERT and retrieve generated keys
JdbcOutboundGateway insertGateway = new JdbcOutboundGateway(
    dataSource,
    "INSERT INTO orders (order_number, amount, status) VALUES (:orderNumber, :amount, :status)"
);
insertGateway.setKeysGenerated(true);

Order newOrder = new Order("ORD-456", new BigDecimal("149.99"), "PENDING");
Message<Order> request = MessageBuilder.withPayload(newOrder).build();
Message<?> reply = (Message<?>) insertGateway.handleRequestMessage(request);

// Reply payload contains generated key(s)
Map<String, Object> keys = (Map<String, Object>) reply.getPayload();
Long generatedId = (Long) keys.get("GENERATED_KEY");

UPDATE then SELECT Pattern

// UPDATE then SELECT - update order and return updated record
JdbcOutboundGateway updateSelectGateway = new JdbcOutboundGateway(
    dataSource,
    "UPDATE orders SET status = :status, updated_at = CURRENT_TIMESTAMP WHERE id = :id",
    "SELECT * FROM orders WHERE id = :id"
);

Map<String, Object> updateParams = Map.of(
    "id", 123L,
    "status", "SHIPPED"
);
Message<?> updateRequest = MessageBuilder.withPayload(updateParams).build();
Message<?> updateReply = (Message<?>) updateSelectGateway.handleRequestMessage(updateRequest);

// Reply payload contains selected order record(s)
List<Map<String, Object>> updatedOrders = (List<Map<String, Object>>) updateReply.getPayload();

Custom Row Mapper

import org.springframework.jdbc.core.RowMapper;

JdbcOutboundGateway gatewayWithMapper = new JdbcOutboundGateway(
    dataSource,
    "INSERT INTO products (name, price) VALUES (:name, :price)",
    "SELECT * FROM products WHERE id = :GENERATED_KEY"
);
gatewayWithMapper.setKeysGenerated(true);
gatewayWithMapper.setRowMapper((rs, rowNum) -> new Product(
    rs.getLong("id"),
    rs.getString("name"),
    rs.getBigDecimal("price")
));

Product newProduct = new Product(null, "Widget", new BigDecimal("29.99"));
Message<Product> productRequest = MessageBuilder.withPayload(newProduct).build();
Message<?> productReply = (Message<?>) gatewayWithMapper.handleRequestMessage(productRequest);

// Reply payload is Product object from row mapper
List<Product> products = (List<Product>) productReply.getPayload();
Product insertedProduct = products.get(0);

SELECT Only (No UPDATE)

// SELECT only (no UPDATE) - query database and return results
JdbcOutboundGateway selectGateway = new JdbcOutboundGateway(
    dataSource,
    "SELECT 1", // Dummy update (no-op)
    "SELECT * FROM orders WHERE customer_id = :payload.customerId ORDER BY created_at DESC"
);
selectGateway.setMaxRows(10);

Customer customer = new Customer(789L);
Message<Customer> selectRequest = MessageBuilder.withPayload(customer).build();
Message<?> selectReply = (Message<?>) selectGateway.handleRequestMessage(selectRequest);

List<Map<String, Object>> customerOrders = (List<Map<String, Object>>) selectReply.getPayload();

Expression-Based Parameter Factories

import org.springframework.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory;

ExpressionEvaluatingSqlParameterSourceFactory requestFactory =
    new ExpressionEvaluatingSqlParameterSourceFactory();
requestFactory.setParameterExpressions(Map.of(
    "orderId", "payload.id",
    "newStatus", "headers['status']",
    "updatedBy", "headers['userId']"
));

ExpressionEvaluatingSqlParameterSourceFactory replyFactory =
    new ExpressionEvaluatingSqlParameterSourceFactory();
replyFactory.setParameterExpressions(Map.of(
    "orderId", "payload.id" // Use original order ID for SELECT
));

JdbcOutboundGateway exprGateway = new JdbcOutboundGateway(
    dataSource,
    "UPDATE orders SET status = :newStatus, updated_by = :updatedBy WHERE id = :orderId",
    "SELECT * FROM orders WHERE id = :orderId"
);
exprGateway.setRequestSqlParameterSourceFactory(requestFactory);
exprGateway.setReplySqlParameterSourceFactory(replyFactory);

Stored Procedure Gateway

import org.springframework.integration.jdbc.StoredProcExecutor;
import org.springframework.integration.jdbc.outbound.StoredProcOutboundGateway;
import org.springframework.integration.jdbc.storedproc.ProcedureParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlOutParameter;
import java.sql.Types;

// Call stored procedure with IN parameters and OUT parameters
StoredProcExecutor executor = new StoredProcExecutor(dataSource);
executor.setStoredProcedureName("PROCESS_ORDER");

// Define IN and OUT parameters
List<SqlParameter> sqlParams = List.of(
    new SqlParameter("order_id", Types.BIGINT),
    new SqlParameter("action", Types.VARCHAR),
    new SqlOutParameter("result_code", Types.INTEGER),
    new SqlOutParameter("result_message", Types.VARCHAR)
);
executor.setSqlParameters(sqlParams);

// Set parameter values from message
List<ProcedureParameter> procParams = List.of(
    new ProcedureParameter("order_id", null, "payload.orderId"),
    new ProcedureParameter("action", null, "payload.action")
);
executor.setProcedureParameters(procParams);

// Create gateway
StoredProcOutboundGateway gateway = new StoredProcOutboundGateway(executor);

// Send request
OrderAction action = new OrderAction(123L, "SHIP");
Message<OrderAction> request = MessageBuilder.withPayload(action).build();
Message<?> reply = (Message<?>) gateway.handleRequestMessage(request);

// Reply payload is Map with OUT parameters
Map<String, Object> results = (Map<String, Object>) reply.getPayload();
Integer resultCode = (Integer) results.get("result_code");
String resultMessage = (String) results.get("result_message");

Stored Function Gateway

// Call stored function (returns single value)
StoredProcExecutor functionExecutor = new StoredProcExecutor(dataSource);
functionExecutor.setStoredProcedureName("CALCULATE_DISCOUNT");
functionExecutor.setIsFunction(true);
functionExecutor.setReturnValueRequired(true);

List<ProcedureParameter> funcParams = List.of(
    new ProcedureParameter("order_amount", null, "payload.amount"),
    new ProcedureParameter("customer_tier", null, "payload.customerTier")
);
functionExecutor.setProcedureParameters(funcParams);

StoredProcOutboundGateway functionGateway = new StoredProcOutboundGateway(functionExecutor);
functionGateway.setExpectSingleResult(true); // Extract return value directly

OrderInfo orderInfo = new OrderInfo(new BigDecimal("500"), "GOLD");
Message<OrderInfo> funcRequest = MessageBuilder.withPayload(orderInfo).build();
Message<?> discountReply = (Message<?>) functionGateway.handleRequestMessage(funcRequest);

// Reply payload is the return value directly (not a Map)
BigDecimal discount = (BigDecimal) discountReply.getPayload();

Integration with Spring Integration

Gateways integrate with Spring Integration's messaging gateway pattern:

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

@Bean
public IntegrationFlow jdbcGatewayFlow(DataSource dataSource) {
    return IntegrationFlow
        .from("requestChannel")
        .handle(outboundGateway(dataSource,
                "INSERT INTO orders (order_number, amount) VALUES (:orderNumber, :amount)",
                "SELECT * FROM orders WHERE order_number = :orderNumber")
            .keysGenerated(true))
        .channel("replyChannel")
        .get();
}

// Use with @MessagingGateway
@MessagingGateway(defaultRequestChannel = "requestChannel")
public interface OrderGateway {
    List<Map<String, Object>> createOrder(Order order);
}

Or using XML configuration:

<int-jdbc:outbound-gateway
    request-channel="requestChannel"
    reply-channel="replyChannel"
    data-source="dataSource"
    update="INSERT INTO orders (order_number, amount) VALUES (:payload.orderNumber, :payload.amount)"
    query="SELECT * FROM orders WHERE id = :GENERATED_KEY"
    keys-generated="true"
    max-rows="1"/>

Deprecated Classes

Important Migration Notice:

The class org.springframework.integration.jdbc.JdbcOutboundGateway 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.JdbcOutboundGateway;

New (Current):

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

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

Key Considerations

  • Request-Reply Pattern: Gateway always produces reply message (unless error occurs)
  • Transaction Management: UPDATE and SELECT execute in same transaction
  • Generated Keys: Available to SELECT query via :GENERATED_KEY parameter
  • Reply Payload Types:
    • No SELECT: Map with generated keys or update count
    • With SELECT: List of Maps or List of domain objects (with custom mapper)
    • Stored proc: Map with result sets, OUT params, and return value
    • expectSingleResult=true: Extract single result from map
  • Error Handling: Exceptions propagate to error channel if configured
  • NULL Results: Empty list if SELECT returns no rows
  • Max Rows: Limit SELECT results to avoid memory issues (must not be null)
  • Performance: Be mindful of transaction duration for complex operations