or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

inbound-adapter.mdindex.mdjpa-executor.mdjpa-operations.mdoutbound-adapter.mdparameter-sources.mdretrieving-gateway.mdupdating-gateway.mdxml-configuration.md
tile.json

parameter-sources.mddocs/

Parameter Sources

Parameter sources provide flexible parameter binding for JPA queries using static values, bean properties, or SpEL expressions. They allow queries to be parameterized dynamically based on message content, headers, or other runtime values.

Key Information for Agents

Required Dependencies:

  • spring-integration-jpa (this package)
  • spring-integration-core is required
  • BeanFactory required for ExpressionEvaluatingParameterSourceFactory (for bean references in expressions)

Default Behaviors:

  • BeanPropertyParameterSource extracts values from bean properties
  • BeanPropertyParameterSourceFactory can mix static parameters with bean properties
  • ExpressionEvaluatingParameterSourceFactory evaluates SpEL expressions
  • Parameter names must match query parameter names (e.g., :paramName in query)
  • Static parameters take precedence over dynamic parameters when both provided
  • usePayloadAsParameterSource=true extracts parameters from payload bean properties

Threading Model:

  • Parameter sources are created per message (thread-safe)
  • ParameterSourceFactory instances should be thread-safe (reusable)
  • Expression evaluation is thread-safe (uses thread-local evaluation context)
  • Bean property extraction is thread-safe

Lifecycle:

  • ParameterSourceFactory instances are typically Spring beans (singleton)
  • ParameterSource instances are created per message (short-lived)
  • No special lifecycle management required

Exceptions:

  • IllegalArgumentException - Invalid parameter name or configuration
  • EvaluationException - SpEL expression evaluation failures
  • PropertyAccessException - Bean property access failures
  • NoSuchParameterException - Parameter not found in source (when accessing)

Edge Cases:

  • Parameter names are case-sensitive (must match query exactly)
  • Missing parameters: hasValue() returns false, getValue() may return null or throw exception
  • BeanPropertyParameterSource uses Spring's BeanWrapper for property access
  • ExpressionEvaluatingParameterSourceFactory evaluates expressions against message context
  • Static parameters in factory are merged with dynamic parameters from input
  • usePayloadAsParameterSource=true requires payload to be a bean (not Map or primitive)
  • Expressions can reference: payload, headers, #pathVariables, #requestParams, Spring beans (@beanName)
  • Null values: expressions can return null, which is passed to query as null
  • Type conversion: parameter values are converted to query parameter types automatically

Core Interfaces

interface ParameterSource {
    boolean hasValue(String paramName)
    Object getValue(String paramName)
}

interface PositionSupportingParameterSource extends ParameterSource {
    Object getValueByPosition(int position)
}

interface ParameterSourceFactory {
    ParameterSource createParameterSource(Object input)
}

JpaParameter Class

class JpaParameter {
    // Constructors
    JpaParameter()
    JpaParameter(String name, Object value, String expression)
    JpaParameter(Object value, String expression)

    // Accessors
    String getName()
    void setName(String name)
    Object getValue()
    void setValue(Object value)
    String getExpression()
    void setExpression(String expression)

    // Parsed expressions
    Expression getSpelExpression()
    Expression getProjectionExpression()
}

Bean Property Parameter Source

Extracts parameter values from properties of a bean.

class BeanPropertyParameterSource implements ParameterSource {
    BeanPropertyParameterSource(Object object)

    boolean hasValue(String paramName)
    Object getValue(String paramName)
    String[] getReadablePropertyNames()
}

Usage Examples

Basic Bean Property Extraction

public class SearchCriteria {
    private String lastName;
    private String department;
    private Integer year;
    // getters and setters
}

SearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");
criteria.setDepartment("Engineering");
criteria.setYear(2024);

BeanPropertyParameterSource paramSource = new BeanPropertyParameterSource(criteria);

// Extract parameters
String lastName = (String) paramSource.getValue("lastName");      // "Smith"
String dept = (String) paramSource.getValue("department");        // "Engineering"
Integer year = (Integer) paramSource.getValue("year");            // 2024

boolean hasEmail = paramSource.hasValue("email");                 // false

With JPA Query

SearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");
criteria.setDepartment("Engineering");

JpaExecutor executor = new JpaExecutor(entityManagerFactory);
executor.setJpaQuery("SELECT s FROM Student s " +
                    "WHERE s.lastName = :lastName " +
                    "AND s.department = :department");
executor.setParameterSource(new BeanPropertyParameterSource(criteria));
executor.afterPropertiesSet();

Object result = executor.poll();

List Readable Properties

BeanPropertyParameterSource paramSource = new BeanPropertyParameterSource(student);
String[] properties = paramSource.getReadablePropertyNames();
// Returns: ["id", "firstName", "lastName", "email", "department", etc.]

Bean Property Parameter Source Factory

Factory for creating BeanPropertyParameterSource instances with optional static parameters.

class BeanPropertyParameterSourceFactory implements ParameterSourceFactory {
    void setStaticParameters(Map<String, ?> staticParameters)
    ParameterSource createParameterSource(Object input)
}

Usage Examples

Basic Factory Usage

BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();

SearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");

ParameterSource paramSource = factory.createParameterSource(criteria);
String lastName = (String) paramSource.getValue("lastName");  // "Smith"

With Static Parameters

BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of(
    "status", "ACTIVE",
    "minGpa", 3.0
));

SearchCriteria criteria = new SearchCriteria();
criteria.setDepartment("Engineering");

ParameterSource paramSource = factory.createParameterSource(criteria);

// Can access both bean properties and static parameters
String dept = (String) paramSource.getValue("department");   // "Engineering"
String status = (String) paramSource.getValue("status");     // "ACTIVE"
Double minGpa = (Double) paramSource.getValue("minGpa");     // 3.0

With JPA Components (DSL)

@Bean
public IntegrationFlow queryFlow(EntityManagerFactory entityManagerFactory) {
    BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
    factory.setStaticParameters(Map.of("status", "ACTIVE"));

    return IntegrationFlow
        .from("searchChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE s.department = :department " +
                         "AND s.status = :status")
                .parameterSourceFactory(factory)
                .usePayloadAsParameterSource(true))
        .channel("resultChannel")
        .get();
}

// Usage
SearchCriteria criteria = new SearchCriteria();
criteria.setDepartment("Engineering");
messagingTemplate.convertAndSend("searchChannel", criteria);

Programmatic Configuration

@Bean
public JpaExecutor executor(EntityManagerFactory emf, BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(emf);
    executor.setJpaQuery("SELECT s FROM Student s " +
                        "WHERE s.lastName = :lastName " +
                        "AND s.status = :status");

    BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
    factory.setStaticParameters(Map.of("status", "ACTIVE"));

    executor.setParameterSourceFactory(factory);
    executor.setUsePayloadAsParameterSource(true);

    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();
    return executor;
}

Expression Evaluating Parameter Source Factory

Creates parameter sources that evaluate SpEL expressions to determine parameter values.

class ExpressionEvaluatingParameterSourceFactory implements ParameterSourceFactory {
    ExpressionEvaluatingParameterSourceFactory()
    ExpressionEvaluatingParameterSourceFactory(BeanFactory beanFactory)

    void setParameters(List<JpaParameter> parameters)
    ParameterSource createParameterSource(Object input)
}

Note: The setParameters method accepts a List<JpaParameter> which provides more flexibility than the Map-based approach. Each JpaParameter can contain a name, static value, and/or SpEL expression.

Usage Examples

Basic Expression Evaluation (List of JpaParameter)

ExpressionEvaluatingParameterSourceFactory factory =
    new ExpressionEvaluatingParameterSourceFactory();

List<JpaParameter> params = Arrays.asList(
    new JpaParameter("lastName", null, "payload.lastName"),
    new JpaParameter("year", null, "payload.enrollmentYear"),
    new JpaParameter("today", null, "T(java.time.LocalDate).now()")
);
factory.setParameters(params);

Message<?> message = MessageBuilder
    .withPayload(searchCriteria)
    .build();

ParameterSource paramSource = factory.createParameterSource(message);

Basic Expression Evaluation (Map - Legacy Style)

For convenience, you can also configure parameters using a Map in DSL contexts, which internally creates JpaParameter objects:

// Note: This Map-based approach is available through DSL helpers
// The actual setParameters method uses List<JpaParameter>
factory.setParameters(Map.of(
    "lastName", "payload.lastName",
    "year", "payload.enrollmentYear",
    "today", "T(java.time.LocalDate).now()"
));

With Message Headers

ExpressionEvaluatingParameterSourceFactory factory =
    new ExpressionEvaluatingParameterSourceFactory();

factory.setParameters(Map.of(
    "studentId", "payload",
    "department", "headers['department']",
    "requestTime", "headers['timestamp']"
));

Message<?> message = MessageBuilder
    .withPayload(123L)
    .setHeader("department", "Engineering")
    .setHeader("timestamp", System.currentTimeMillis())
    .build();

ParameterSource paramSource = factory.createParameterSource(message);

Complex Expressions

ExpressionEvaluatingParameterSourceFactory factory =
    new ExpressionEvaluatingParameterSourceFactory();

factory.setParameters(Map.of(
    "fullName", "payload.firstName + ' ' + payload.lastName",
    "upperDept", "payload.department.toUpperCase()",
    "ageCheck", "payload.birthDate != null ? " +
                "T(java.time.Period).between(payload.birthDate, T(java.time.LocalDate).now()).getYears() : null",
    "isEligible", "payload.credits >= 120 and payload.gpa >= 3.0"
));

With Bean References

@Component
public class QueryConfig {
    public String getDefaultStatus() {
        return "ACTIVE";
    }

    public int getMaxResults() {
        return 100;
    }
}

ExpressionEvaluatingParameterSourceFactory factory =
    new ExpressionEvaluatingParameterSourceFactory(beanFactory);

factory.setParameters(Map.of(
    "status", "@queryConfig.getDefaultStatus()",
    "maxCount", "@queryConfig.getMaxResults()",
    "department", "payload.department"
));

With JPA Components (DSL)

@Bean
public IntegrationFlow expressionQueryFlow(
        EntityManagerFactory entityManagerFactory,
        BeanFactory beanFactory) {

    ExpressionEvaluatingParameterSourceFactory factory =
        new ExpressionEvaluatingParameterSourceFactory(beanFactory);

    factory.setParameters(Map.of(
        "lastName", "payload.lastName",
        "minYear", "payload.yearFrom",
        "maxYear", "payload.yearTo"
    ));

    return IntegrationFlow
        .from("advancedSearchChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE s.lastName = :lastName " +
                         "AND s.enrollmentYear BETWEEN :minYear AND :maxYear")
                .parameterSourceFactory(factory)
                .usePayloadAsParameterSource(true))
        .channel("searchResultsChannel")
        .get();
}

Parameter Expression Evaluator

Helper class for evaluating parameter expressions.

class ParameterExpressionEvaluator extends AbstractExpressionEvaluator {
    StandardEvaluationContext getEvaluationContext()
    <T> T evaluateExpression(String expression, Object input, Class<T> expectedType)
}

Usage Example

ParameterExpressionEvaluator evaluator = new ParameterExpressionEvaluator();

Message<?> message = MessageBuilder.withPayload(student).build();

String result = evaluator.evaluateExpression(
    "payload.lastName",
    message,
    String.class
);

JpaParameter Usage

The JpaParameter class combines static values and expressions for query parameters.

Static Parameter

JpaParameter param = new JpaParameter();
param.setName("status");
param.setValue("ACTIVE");

Or using constructor:

JpaParameter param = new JpaParameter("status", "ACTIVE", null);

Expression Parameter

JpaParameter param = new JpaParameter();
param.setName("studentId");
param.setExpression("payload");

Or using constructor:

JpaParameter param = new JpaParameter("studentId", null, "payload");

Mixed Parameters

List<JpaParameter> parameters = Arrays.asList(
    new JpaParameter("status", "ACTIVE", null),              // Static
    new JpaParameter("department", null, "payload.dept"),     // Expression
    new JpaParameter("year", null, "headers['academicYear']") // From header
);

executor.setJpaParameters(parameters);

With DSL

@Bean
public IntegrationFlow mixedParamsFlow(EntityManagerFactory entityManagerFactory) {
    return IntegrationFlow
        .from("queryChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE s.status = :status " +
                         "AND s.department = :department " +
                         "AND s.year = :year")
                .parameter("status", "ACTIVE")                    // Static
                .parameterExpression("department", "payload.dept") // Expression
                .parameterExpression("year", "headers['year']"))  // From header
        .channel("resultsChannel")
        .get();
}

Complete Integration Examples

Example 1: Bean Property with Static Defaults

@Bean
public IntegrationFlow studentSearchFlow(EntityManagerFactory entityManagerFactory) {
    BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
    factory.setStaticParameters(Map.of(
        "status", "ACTIVE",
        "minGpa", 2.0
    ));

    return IntegrationFlow
        .from("studentSearchChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE s.department = :department " +
                         "AND s.status = :status " +
                         "AND s.gpa >= :minGpa")
                .parameterSourceFactory(factory)
                .usePayloadAsParameterSource(true))
        .channel("searchResultsChannel")
        .get();
}

// Usage
public class StudentSearch {
    private String department;
    // No need for status or minGpa - they have defaults
}

StudentSearch search = new StudentSearch();
search.setDepartment("Engineering");
messagingTemplate.convertAndSend("studentSearchChannel", search);

Example 2: Dynamic Expression-Based Query

@Bean
public IntegrationFlow dynamicQueryFlow(
        EntityManagerFactory entityManagerFactory,
        BeanFactory beanFactory) {

    ExpressionEvaluatingParameterSourceFactory factory =
        new ExpressionEvaluatingParameterSourceFactory(beanFactory);

    factory.setParameters(Map.of(
        "searchTerm", "payload.search",
        "searchPattern", "'%' + payload.search + '%'",
        "department", "payload.department ?: 'ALL'",
        "startDate", "payload.dateFrom ?: T(java.time.LocalDate).now().minusMonths(1)",
        "endDate", "payload.dateTo ?: T(java.time.LocalDate).now()"
    ));

    return IntegrationFlow
        .from("dynamicSearchChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE (s.firstName LIKE :searchPattern " +
                         "       OR s.lastName LIKE :searchPattern) " +
                         "AND (:department = 'ALL' OR s.department = :department) " +
                         "AND s.enrollmentDate BETWEEN :startDate AND :endDate")
                .parameterSourceFactory(factory)
                .usePayloadAsParameterSource(true))
        .channel("dynamicResultsChannel")
        .get();
}

Example 3: Combining Multiple Parameter Approaches

@Bean
public IntegrationFlow combinedParamsFlow(EntityManagerFactory entityManagerFactory) {
    BeanPropertyParameterSourceFactory beanFactory = new BeanPropertyParameterSourceFactory();
    beanFactory.setStaticParameters(Map.of("defaultStatus", "ACTIVE"));

    return IntegrationFlow
        .from("combinedChannel")
        .handle(Jpa.retrievingGateway(entityManagerFactory)
                .jpaQuery("SELECT s FROM Student s " +
                         "WHERE s.lastName = :lastName " +
                         "AND s.status = :status " +
                         "AND s.year = :year " +
                         "AND s.credits >= :minCredits")
                .parameterSourceFactory(beanFactory)
                .usePayloadAsParameterSource(true)
                .parameter("minCredits", 30)  // Direct static parameter
                .parameterExpression("year", "headers['academicYear']"))  // Header
        .channel("combinedResultsChannel")
        .get();
}

Example 4: Inbound Adapter with Parameter Source

@Bean
public IntegrationFlow inboundWithParamsFlow(EntityManagerFactory entityManagerFactory) {
    return IntegrationFlow
        .from(Jpa.inboundAdapter(entityManagerFactory)
                .jpaQuery("SELECT t FROM Task t " +
                         "WHERE t.status = :status " +
                         "AND t.priority >= :minPriority")
                .parameterSource(new BeanPropertyParameterSource(
                    Map.of("status", "PENDING", "minPriority", 5)))
                .maxResults(100)
                .deleteAfterPoll(true),
            e -> e.poller(Pollers.fixedDelay(10000)))
        .channel("taskChannel")
        .get();
}

Utility Class

final class ExpressionEvaluatingParameterSourceUtils {
    static List<JpaParameter> convertStaticParameters(Map<String, ?> staticParameters)
}

Usage

Map<String, Object> staticParams = Map.of(
    "status", "ACTIVE",
    "type", "STUDENT",
    "year", 2024
);

List<JpaParameter> jpaParams =
    ExpressionEvaluatingParameterSourceUtils.convertStaticParameters(staticParams);

executor.setJpaParameters(jpaParams);

Best Practices

Choose the Right Parameter Source

  1. Static values - Use JpaParameter with values
  2. Bean properties - Use BeanPropertyParameterSourceFactory
  3. Complex expressions - Use ExpressionEvaluatingParameterSourceFactory
  4. Mixed scenarios - Combine factory with static parameters

Expression Safety

When using expressions, be cautious of:

  • Null pointer exceptions (use safe navigation: ?.)
  • Type mismatches
  • Expression complexity
// Good - safe navigation
"payload?.department"

// Good - null coalescing
"payload.year ?: 2024"

// Good - type checking
"payload instanceof T(com.example.SearchCriteria) ? payload.department : null"

Performance Considerations

  1. Bean property extraction is faster than expression evaluation
  2. Static parameters are fastest
  3. Cache ParameterSourceFactory instances
  4. Avoid complex expressions in high-throughput scenarios

Parameter Naming

Use consistent, descriptive parameter names:

// Good
.parameter("studentId", "payload.id")
.parameter("enrollmentYear", "payload.year")

// Avoid
.parameter("id", "payload.id")
.parameter("year", "payload.year")

Error Handling

@Bean
public ExpressionEvaluatingParameterSourceFactory safeFactory(BeanFactory beanFactory) {
    ExpressionEvaluatingParameterSourceFactory factory =
        new ExpressionEvaluatingParameterSourceFactory(beanFactory);

    factory.setParameters(Map.of(
        "safeDept", "payload.department != null ? payload.department : 'UNKNOWN'",
        "safeYear", "payload.year != null ? payload.year : T(java.time.Year).now().getValue()"
    ));

    return factory;
}