Behavior Implementations
RAP / CDS / ODataadvanced

Behavior Implementations Part 4: Save Sequence, Late Numbering and Unmanaged Save

Key takeaway

Know what belongs in the saver class of each implementation type: when a managed BO needs a saver at all, how late numbering works with adjust numbers, and how save modified implements additional save and unmanaged save.

Explanation

📌 KEY POINTS
✅ Plain managed BOs need no saver class.

✅ Late numbering → adjust_numbers; extra persistence → save_modified.

✅ save_modified runs once per successful save — ideal for change logs.

⚠️ No checks, commits or EML changes in the save phase.

▶ WHERE WE ARE
Parts 1–3 covered handler classes. The saver class (inheriting from cl_abap_behavior_saver) contains code for the save sequence. Unmanaged RAP Part 4 covers the full unmanaged saver in depth; this lesson covers the saver from the implementation-type point of view, especially for managed BOs.

━━━━━━━━━━━━━━━━━━━━
▶ THE SAVER METHODS (IN ORDER)

  • finalize — last derivations; may report errors.
  • check_before_save — final checks; may report errors (stops the save).
  • cleanup_finalize — runs if finalize or check_before_save failed; undo derivations, keep the buffer.
  • adjust_numbers — late numbering: assign final keys.
  • save — unmanaged BOs: write the buffer. Point of no return.
  • save_modified — managed BOs with additional save or unmanaged save: receives the changes to persist.
  • cleanup — after the commit; clear buffers.

You only redefine the methods you need.

━━━━━━━━━━━━━━━━━━━━
▶ WHEN DOES A MANAGED BO NEED A SAVER?

A plain managed BO needs no saver: on-save determinations and validations are handler methods, and the framework writes the tables. You add a saver only for:

  1. Late numbering → adjust_numbers.
  2. Additional save → save_modified (framework also writes).
  3. Unmanaged save → save_modified (framework does not write).

━━━━━━━━━━━━━━━━━━━━
▶ LATE NUMBERING IN A MANAGED BO

Declare "late numbering" in the entity header. During the interaction phase, new instances are identified by a preliminary ID (%pid) and a temporary key. In adjust_numbers, RAP passes the new instances in mapped; you assign the final key fields (for example from a number range) for each entry. Use it when numbers must be gap-free or can only be assigned once the save is certain. Drawing the number as late as possible means failed or abandoned creates never consume numbers.

SAVE_MODIFIED — ONE METHOD, TWO USES
save_modified receives three structures, one component per entity:

  • create-<entity> — complete new instances.
  • update-<entity> — changed instances; %control shows which fields changed. With "with full data" in the BDEF you receive all fields.
  • delete-<entity> — keys of deleted instances.

Additional save: the framework already writes the BO tables; use save_modified to write something extra in the same LUW — a change log, an outbox table for events, statistics.
Unmanaged save: the framework does not write; translate the three structures into calls to the persistence API (e.g. update-task function modules).

"MAPPING FROM ENTITY" (CORRESPONDING … MAPPING FROM ENTITY) uses the BDEF mapping block to convert entity fields to table columns.

━━━━━━━━━━━━━━━━━━━━
▶ SAVE-PHASE RULES (ALL TYPES)

  • No errors after check_before_save — save and save_modified must not fail.
  • No COMMIT WORK, BAPI_TRANSACTION_COMMIT or ROLLBACK.
  • No EML MODIFY on BOs during save; reading is fine.
  • No synchronous calls that commit elsewhere; use update tasks, bgRFC, the background processing framework or business events after commit.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

  1. Decide whether you need a saver (late numbering, additional save, unmanaged save).
  2. Generate the saver class with the quick fix; redefine only needed methods.
  3. Implement adjust_numbers or save_modified as in the code example.
  4. Test: failed validation → nothing written; successful save → change log entries exist exactly once.

━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER PART 4

The right saver for each implementation type. Part 5 covers performance, testing and debugging.

Code example

ABAP Code
// ===== BDEF: late numbering + additional save (managed) =====// Variant for this lesson: the KEY of ZR_PurchReq is RequestID (no UUID),// because late numbering assigns key fields.define behavior for ZR_PurchReq alias PurchaseRequestpersistent table zpr_head with additional save     // framework writes + save_modifiedlate numbering                                      // final key in adjust_numberslock masterauthorization master ( global, instance )etag master LocalLastChangedAt{  create; update; delete;  field ( readonly ) RequestID;                     // RequestID = semantic key assigned late  mapping for zpr_head { /* ... */ }} " ===== Saver class =====CLASS lsc_zr_purchreq DEFINITION INHERITING FROM cl_abap_behavior_saver.  PROTECTED SECTION.    METHODS adjust_numbers REDEFINITION.    METHODS save_modified  REDEFINITION.ENDCLASS. CLASS lsc_zr_purchreq IMPLEMENTATION.   METHOD adjust_numbers.    " mapped-purchaserequest contains one entry per new instance (%pid + temporary key)    CHECK mapped-purchaserequest IS NOT INITIAL.     cl_numberrange_runtime=>number_get(      EXPORTING nr_range_nr       = '01'                object            = 'ZPR_REQ'                quantity          = CONV #( lines( mapped-purchaserequest ) )      IMPORTING number            = DATA(last_number)                returned_quantity = DATA(qty) ).     DATA(next) = CONV int8( last_number ) - qty.    LOOP AT mapped-purchaserequest ASSIGNING FIELD-SYMBOL(<new>).      next += 1.      <new>-RequestID = next.                        " final key assigned here    ENDLOOP.  ENDMETHOD.   METHOD save_modified.    " Additional save: framework has written ZPR_HEAD; add a change log in the same LUW    DATA log TYPE STANDARD TABLE OF zpr_changelog WITH EMPTY KEY.    DATA now TYPE timestampl.    GET TIME STAMP FIELD now.    DATA(user) = cl_abap_context_info=>get_user_technical_name( ).     log = VALUE #( BASE log FOR c IN create-purchaserequest                   ( request_id = c-RequestID  change_type = 'C'  changed_by = user  changed_at = now ) ).    log = VALUE #( BASE log FOR u IN update-purchaserequest                   ( request_id = u-RequestID  change_type = 'U'  changed_by = user  changed_at = now ) ).    log = VALUE #( BASE log FOR d IN delete-purchaserequest                   ( request_id = d-RequestID  change_type = 'D'  changed_by = user  changed_at = now ) ).     IF log IS NOT INITIAL.      INSERT zpr_changelog FROM TABLE @log.          " own table, same LUW, no COMMIT    ENDIF.  ENDMETHOD. ENDCLASS. " Unmanaged save variant: BDEF "with unmanaged save" (no persistent table) and" save_modified calls the persistence API instead, e.g."   CALL FUNCTION 'Z_PR_POST' IN UPDATE TASK"     EXPORTING it_create = CORRESPONDING zpr_head_tt( create-purchaserequest MAPPING FROM ENTITY ).

Real project scenario

A procurement team needed an audit log of every change to purchase requests. The first implementation wrote the log inside a determination on modify — which ran several times per transaction and also for changes that later failed validation, producing duplicate and phantom log entries.
Moving the logging to save_modified with "with additional save" solved it: save_modified runs once per successful save, after all validations passed, in the same LUW as the business data. Log entries now exist if and only if the change was persisted.

Common mistakes

  • Adding a saver to a plain managed BO without a reason.
  • Writing logs or follow-up data in determinations instead of save_modified.
  • Performing checks in save or save_modified.
  • Committing in the saver.
  • Using additional save when the standard write must be replaced (that is unmanaged save).
  • Forgetting that update-<entity> only contains changed fields unless "with full data" is set.
  • Drawing late numbers somewhere other than adjust_numbers.

Best practices

  • Add a saver only for late numbering, additional save or unmanaged save.
  • Use save_modified for anything that must exist exactly when the BO is saved.
  • Keep save-phase code simple and failure-free; all checks before.
  • Use MAPPING FROM ENTITY to reuse the BDEF mapping.
  • Test failed and successful saves and verify side tables for both.

Interview angle

Questions: "Which saver methods exist and in which order?", "When does a managed BO need a saver?", "What is save_modified used for?", "How does late numbering work?"
Strong answer: finalize, check_before_save (cleanup_finalize on failure), adjust_numbers, save/save_modified, cleanup. Managed BOs need a saver only for late numbering, additional save or unmanaged save. save_modified receives create/update/delete per entity; additional save adds work on top of the framework's write, unmanaged save replaces it. adjust_numbers assigns final keys for instances identified by %pid during interaction.

Practice this lesson

Quick check

1. Saver code calls a remote REST service synchronously during save. Why is this a problem?

2. In an unmanaged RAP implementation, the developer chooses NOT to implement a LOCK method in the behavior pool. What is the consequence for concurrent access to the business object?

3. A behavior definition includes both a managed implementation type for standard operations and a custom action implemented in a behavior pool. Which statement about combining managed and self-implemented elements in the same behavior definition is correct?

Interview questions

1. When does a managed RAP BO need a saver class, and what goes into adjust_numbers and save_modified?

Answer it in your own words first, then compare.

2. An audit log written in a determination contains duplicate entries and entries for changes that were never saved. How do you fix it?

Answer it in your own words first, then compare.

Practice more with Interview AI →

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

Interview questions on this topic

Related SAP topics