BAPI Part 9: BAPI Uploads — Idempotency and Safe Retry
Build reliable BAPI uploads and interfaces: one LUW per document, idempotency keys to prevent duplicates on reruns, status tables, locking, restart after failures, handling "success but unknown result" cases, and parallel processing considerations.
Explanation
📌 KEY POINTS
✅ Idempotency: running the same input twice must not create two documents — store an external key per document and check it before calling the BAPI.
✅ A status table (external key → status, SAP document, messages) makes reruns safe: skip done rows, retry failed ones.
✅ One LUW per document: BAPI → evaluate RETURN → commit/rollback → update status (in the same or next LUW deliberately).
⚠️ The dangerous case is "commit done, status not saved" (crash between them) — design the check so a rerun detects the existing document.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Parts 2–8 called BAPIs correctly. Real uploads also fail midway, get restarted and receive duplicate files.
━━━━━━━━━━━━━━━━━━━━
▶ 1. WHY DUPLICATES HAPPEN
- A job is restarted after a timeout.
- The same file is uploaded twice.
- Middleware retries after a network error although SAP already committed.
━━━━━━━━━━━━━━━━━━━━
▶ 2. IDEMPOTENCY KEY
- Use a business key from the source: external order ID, file name + row, EDI reference.
- Store it in the SAP document where possible (e.g. customer PO number, reference field, custom field) and in a Z status table.
- Before the BAPI: check the status table and, if needed, the document itself (SELECT on reference field).
━━━━━━━━━━━━━━━━━━━━
▶ 3. STATUS TABLE DESIGN
ZUPL_STATUS: EXTKEY (key), STATUS (N new, P processing, S success, E error), SAP_DOC, RUN_ID, MESSAGES reference (SLG1 log number), CHANGED_AT/BY.
━━━━━━━━━━━━━━━━━━━━
▶ 4. PROCESSING SEQUENCE PER DOCUMENT
- Lock the external key (ENQUEUE on the status table) to avoid parallel duplicates.
- If status S → skip.
- Call BAPI; evaluate RETURN.
- Error → rollback, status E with log.
- Success → commit WAIT; then write status S with document number and commit.
- On restart with status P: look for the document by reference before retrying.
━━━━━━━━━━━━━━━━━━━━
▶ 5. PARALLEL PROCESSING
- Split by key ranges or use aRFC/parallel jobs; locks on master data (e.g. same material) can cause errors — retry those later.
- Keep packages small; log per package.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
- Create a status table as described (sandbox).
- Implement the processing sequence (code example) and run a file twice.
- Kill the job midway and restart: no duplicates.
✅ CHECKPOINT: your uploads can be restarted safely without duplicates or lost documents.
Code example
* ===== Idempotent processing of one upload row =====* ZUPL_STATUS: MANDT, EXTKEY (CHAR40, key), STATUS (CHAR1), VBELN, LOGNUMBER, CHANGED_AT FORM process_row USING is_row TYPE zs_order_row. DATA: ls_status TYPE zupl_status, lv_vbeln TYPE vbeln_va, lv_error TYPE abap_bool. * 1) Lock the external key (lock object EZUPL_STATUS) CALL FUNCTION 'ENQUEUE_EZUPL_STATUS' EXPORTING extkey = is_row-extkey EXCEPTIONS foreign_lock = 1 OTHERS = 2. IF sy-subrc <> 0. RETURN. " processed in parallel elsewhere ENDIF. * 2) Already done? (status table, then the document itself) SELECT SINGLE * FROM zupl_status INTO ls_status WHERE extkey = is_row-extkey. IF ls_status-status = 'S'. PERFORM unlock USING is_row-extkey. RETURN. ENDIF. SELECT SINGLE vbeln FROM vbak INTO lv_vbeln WHERE bstnk = is_row-extkey AND kunnr = is_row-kunnr. " reference stored in PO no. IF sy-subrc = 0. PERFORM set_status USING is_row-extkey 'S' lv_vbeln. " crash after commit earlier PERFORM unlock USING is_row-extkey. RETURN. ENDIF. * 3) Create the order (Part 2), PURCH_NO_C = extkey PERFORM create_order USING is_row CHANGING lv_vbeln lv_error. * 4) Commit or roll back, then persist status IF lv_error = abap_true. CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'. PERFORM set_status USING is_row-extkey 'E' space. ELSE. CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'. PERFORM set_status USING is_row-extkey 'S' lv_vbeln. ENDIF. PERFORM unlock USING is_row-extkey.ENDFORM. FORM set_status USING iv_key TYPE zupl_status-extkey iv_stat TYPE c iv_vbeln TYPE vbeln_va. DATA ls_status TYPE zupl_status. ls_status-extkey = iv_key. ls_status-status = iv_stat. ls_status-vbeln = iv_vbeln. GET TIME STAMP FIELD ls_status-changed_at. MODIFY zupl_status FROM ls_status. COMMIT WORK. " own LUW for the statusENDFORM. FORM unlock USING iv_key TYPE zupl_status-extkey. CALL FUNCTION 'DEQUEUE_EZUPL_STATUS' EXPORTING extkey = iv_key.ENDFORM. FORM create_order USING is_row TYPE zs_order_row CHANGING cv_vbeln TYPE vbeln_va cv_error TYPE abap_bool. " mapping + BAPI_SALESORDER_CREATEFROMDAT2 + RETURN evaluation (Parts 2, 4, 6)ENDFORM.Real project scenario
A nightly order upload job was cancelled by a system restart after 3,000 of 5,000 orders. Operations restarted it the next morning, creating 3,000 duplicate orders that customers received as double deliveries.
The redesigned upload stored the external order ID in the customer PO number and in a status table, checked both before each BAPI call and locked each key. Later restarts processed only the missing orders, and duplicate complaints disappeared.
Common mistakes
- No idempotency key — reruns create duplicates.
- One commit for the whole file (one error loses everything, locks held).
- Status written before commit (shows success for rolled-back documents).
- Ignoring the crash-after-commit case.
- Parallel runs without locks on the key.
Best practices
- Store a source key in the document and a status table.
- Lock per key; one LUW per document.
- Check status and document before retrying.
- Log RETURN per key (SLG1).
- Design restart procedures and test them.
Interview angle
Questions: "How do you avoid duplicate documents when an upload is restarted?", "How do you design a BAPI upload?"
Strong answer: idempotency key from source stored in document (e.g. PO number/reference) and a status table; lock per key; check status and existing document before calling; one LUW per document with RETURN evaluation, commit WAIT/rollback, then status update; handle crash-after-commit; logging; parallel processing with key ranges and lock-aware retries.
Practice this lesson
Quick check
1. In an idempotent upload, when should the success status be written?
2. An upload job is restarted after a crash. What prevents duplicate documents?
3. Why should each upload row lock its external key (ENQUEUE) before calling the BAPI?
Interview questions
1. How do you design a robust batch that posts thousands of BAPIs?
Answer it in your own words first, then compare.
2. A middleware interface retried a BAPI-based order creation after an error response, and customers received duplicate orders. How do you design the interface to be retry-safe?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.
More lessons in this topic
- BAPI Part 1: What a BAPI Is and How to Find One
- BAPI Part 10: "Success but No Document" — Debugging BAPI Problems
- BAPI Part 2: Calling a BAPI — Inputs, X Structures, RETURN and Commit
- BAPI Part 3: BAPI vs RFC vs OData
- BAPI Part 4: Understanding BAPI RETURN Messages
- BAPI Part 5: Commit, WAIT, Rollback and the SAP LUW
- BAPI Part 6: Mapping Header, Item, Partner and Schedule Data
- BAPI Part 7: Test Run, Simulation and Safe BAPI Execution