Skip to main content

Building iFlows in SAP BTP Integration Suite: A Developer's Guide - AegeanFlows

Hands-on guide to designing, building, and operating SAP Integration Suite iFlows — covering message mapping, adapters, error handling, and production monitoring.

Kostas Polakis March 5, 2026 7 min read
SAP BTP iFlow Integration Suite SAP Development

What Is an iFlow?

An iFlow (Integration Flow) is the fundamental unit of work in SAP BTP Integration Suite. It is a graphical definition of a message processing pipeline: where a message comes from, how it is transformed, and where it goes. Each iFlow runs in a managed runtime; you define the logic, SAP manages the infrastructure.

This guide is for developers building or reviewing iFlows in production. It assumes you are familiar with basic BTP concepts and have access to Integration Suite.


iFlow Anatomy

Every iFlow consists of:

  1. Trigger (Start Event) — What initiates the flow: a scheduled timer, an inbound HTTP call, an SFTP file, or an event from Event Mesh
  2. Processing steps — Content modifier, message mapping, script, router, splitter
  3. Receiver (End) — Where the processed message goes: HTTP, OData, SFTP, AMQP, etc.

A minimal iFlow for polling an OData endpoint and sending to a REST API looks like:

[Timer: every 15 min]
    → [Request-Reply: GET /sap/opu/odata/sap/API_BUSINESS_PARTNER]
    → [Message Mapping: BP → res.partner]
    → [HTTP Request: POST /web/dataset/call_kw (Odoo)]
    → [End]

Adapters: Connecting to SAP Systems

OData Adapter (Inbound from S4HANA)

The OData receiver adapter is the standard way to call S4HANA APIs. Configuration essentials:

Adapter: OData
Method: GET
Resource Path: /sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner
Query Options: $filter=LastChangeDate gt datetime'${property.lastRunTime}'
               &$select=BusinessPartner,BusinessPartnerFullName,Country
               &$top=500
               &$skiptoken=${property.skipToken}

Key pitfalls:

  • Always paginate using $top and $skiptoken. SAP OData services have a default page limit of 1,000 records; large datasets require multiple requests.
  • Use $select to limit returned fields. Returning all fields on A_BusinessPartner fetches ~80 fields when you may only need 6.
  • Store LastChangeDate as an iFlow property to enable delta processing.

RFC/BAPI Adapter

For older SAP systems or BAPIs not exposed as OData:

Adapter: RFC
Function Module: BAPI_BUSINESSPARTNER_GETLIST
Import Parameters:
  ADDRESSDATA: required
  CENTRAL_DATA: required
Export Parameters:
  BUSINESSPARTNER: table → map to output message

The RFC adapter requires the Cloud Connector configured to expose the function module. Restrict exposed function modules to only those needed — this is both a security and a maintenance best practice.


Message Mapping: Structural Transformation

Message mapping in Integration Suite uses a graphical mapper or XSLT. For complex transformations, XSLT is more maintainable than the graphical mapper.

XSLT Example: SAP Business Partner → Odoo Partner

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="/BusinessPartners">
    <OdooPartners>
      <xsl:for-each select="A_BusinessPartner">
        <partner>
          <x_sap_bp_id><xsl:value-of select="BusinessPartner"/></x_sap_bp_id>
          <name><xsl:value-of select="BusinessPartnerFullName"/></name>
          <is_company>
            <xsl:choose>
              <xsl:when test="BusinessPartnerCategory = '2'">true</xsl:when>
              <xsl:otherwise>false</xsl:otherwise>
            </xsl:choose>
          </is_company>
          <country_code><xsl:value-of select="Country"/></country_code>
          <vat><xsl:value-of select="TaxNumber1"/></vat>
        </partner>
      </xsl:for-each>
    </OdooPartners>
  </xsl:template>
</xsl:stylesheet>

Groovy Script for Dynamic Logic

When the transformation requires business logic (lookups, conditional branching, date arithmetic), use a Groovy script step:

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.json.JsonSlurper
import groovy.json.JsonOutput

def Message processData(Message message) {
    def body = message.getBody(String.class)
    def partners = new JsonSlurper().parseText(body)
    
    def odooPayload = []
    
    partners.each { bp ->
        // Skip inactive BPs
        if (bp.BusinessPartnerIsBlocked == 'X') return
        
        // Map payment terms
        def paymentTermId = mapPaymentTerms(bp.PaymentTerms)
        
        odooPayload << [
            x_sap_bp_id: bp.BusinessPartner,
            name: bp.BusinessPartnerFullName?.trim(),
            vat: bp.TaxNumber1,
            property_payment_term_id: paymentTermId,
            comment: "Synced from SAP at ${new Date().format('yyyy-MM-dd HH:mm')}"
        ]
    }
    
    message.setBody(JsonOutput.toJson([
        jsonrpc: "2.0",
        method: "call",
        params: [
            model: "res.partner",
            method: "create",
            args: [odooPayload],
            kwargs: [:]
        ]
    ]))
    
    message.setHeader("Content-Type", "application/json")
    return message
}

def mapPaymentTerms(String sapTerm) {
    def termMap = [
        'Z001': 1,  // Immediate
        'Z030': 2,  // 30 days
        'Z060': 3,  // 60 days
        'ZN30': 4,  // Net 30
    ]
    return termMap[sapTerm] ?: 1
}

Error Handling: The Right Way

Error handling in iFlows is where most implementations fall short. There are three layers to get right:

1. Retry with Exponential Backoff

For transient errors (network timeout, target system temporarily unavailable):

Exception Subprocess:
  → Check error type (HTTP status code)
  → If 5xx or timeout:
      → Wait step (delay: ${property.retryDelay})
      → Increment retry counter
      → If counter < 3: re-route to main flow
      → Else: send to dead letter queue
  → If 4xx (client error):
      → Log error details
      → Send alert
      → Do NOT retry (data problem, not infrastructure)

2. Dead Letter Queue

Messages that exhaust retries should not be silently dropped. Route them to an SFTP folder or an AMQP queue for manual review:

Dead Letter Handler:
  → Persist original message to SFTP: /errors/${date}/failed_${messageId}.xml
  → Send email alert to integration.ops@company.com
  → Log to monitoring with error category

3. Partial Failure in Batch Processing

When an iFlow processes 500 records in a batch and record 237 fails:

def processWithPartialFailure(Message message) {
    def records = parseInput(message)
    def results = [success: [], failed: []]
    
    records.each { record ->
        try {
            processRecord(record)
            results.success << record.id
        } catch (Exception e) {
            results.failed << [id: record.id, error: e.message]
            // Log but continue — don't let one bad record kill the batch
        }
    }
    
    if (results.failed) {
        // Alert but don't throw — successful records already processed
        sendFailureAlert(results.failed)
    }
    
    message.setBody(JsonOutput.toJson(results))
    return message
}

Pagination: Handling Large Datasets

SAP OData services paginate at 1,000 records by default. Your iFlow must handle pagination:

[Start: Timer]
→ [Content Modifier: set property skipToken = '']
→ [Loop: while skipToken != 'DONE']
    → [HTTP GET: $skiptoken=${property.skipToken}]
    → [Process records]
    → [Groovy: extract next skipToken from @odata.nextLink]
    → [Content Modifier: update skipToken property]
→ [End]

The Groovy script to extract the next skip token:

def extractSkipToken(Message message) {
    def response = new JsonSlurper().parseText(message.getBody(String.class))
    def nextLink = response['@odata.nextLink']
    
    if (nextLink) {
        def matcher = nextLink =~ /\$skiptoken=([^&]+)/
        message.setProperty('skipToken', matcher ? matcher[0][1] : 'DONE')
    } else {
        message.setProperty('skipToken', 'DONE')
    }
    return message
}

Monitoring and Operations

Message Processing Log

Enable message processing log for all production iFlows. Configure log level:

  • INFO — Successful runs (keep 7 days)
  • DEBUG — Full payload logging (keep 2 days, GDPR note: disable for personal data flows)
  • ERROR — Failures (keep 30 days)

Access logs via: Monitor → Integrations → Message Processing Log

Custom Metrics with External Log

For integration dashboards (Grafana, Dynatrace), emit structured logs from Groovy scripts:

def emitMetric(Message message, String eventType, int recordCount) {
    def metric = [
        timestamp: new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
        iflow: "BP_SAP_TO_ODOO",
        event: eventType,
        records_processed: recordCount,
        duration_ms: System.currentTimeMillis() - message.getProperty('startTime'),
        environment: "PROD"
    ]
    // Log to external system via HTTP adapter
    message.setProperty('metricsPayload', JsonOutput.toJson(metric))
}

Alerting: Don’t Alert on Every Failure

Alert fatigue is real. Configure alerts at the iFlow level:

  • Alert only when failure rate exceeds 5% in a 15-minute window
  • Alert immediately for dead letter queue additions
  • Alert on iFlow deployment failures

Performance Tuning Checklist

Before going live, verify:

  • $select used on all OData calls — no unnecessary field fetching
  • Pagination implemented for all datasets > 500 records
  • Batch size set appropriately (SAP: 500 records/call; Odoo: 1,000 records/call)
  • Retry logic implemented with exponential backoff
  • Dead letter queue configured
  • Message processing log retention configured
  • iFlow parameters externalised (no hardcoded URLs or credentials)
  • Deployed to test environment before production
  • Smoke test run with 1 record, then 100 records, then full load

Conclusion

iFlows are the most productive way to build enterprise integrations when you are operating within the SAP ecosystem. The graphical editor handles the infrastructure; you focus on transformation logic and error handling. The patterns in this guide — XSLT mappings, Groovy scripts for business logic, proper retry and dead letter handling — are what separate integrations that work in demos from integrations that run reliably in production for years.

Building an iFlow for your SAP or Odoo integration? Contact AegeanFlows.


Kostas Polakis — SAP BTP Integration Architect, Athens, Greece.