Determinations and Validations are the two mechanisms in RAP for automatically executing business logic. Determinations set values, validations check them. Understanding the timing correctly (when is what executed) is crucial for error-free RAP Business Objects.
The Difference at a Glance
+-------------------------------------------------------------+| User Action (Create/Update) |+-----------------------+-------------------------------------+ | v +-----------------------+ | DETERMINATION | <- Automatically sets values | "What's missing?" | (Status, numbers, defaults) +-----------+-----------+ | v +-----------------------+ | VALIDATION | <- Checks business rules | "Is it correct?" | (Date logic, required fields) +-----------+-----------+ | v (only on success) +-----------------------+ | Save to Database | +-----------------------+| Aspect | Determination | Validation |
|---|---|---|
| Purpose | Set/calculate values | Check values |
| Timing | on modify or on save | on save |
| Changes | May modify entity | Read-only, no changes |
| Errors | No direct errors | Fills FAILED & REPORTED |
| Example | Set status to ‘O’ | Check end date after begin |
| Order | First (before Validation) | After (after Determination) |
Determinations: Automatic Value Setting
When to Use Determinations?
Perfect for:
- Setting default values (Status = ‘Open’)
- Calculating fields (TotalPrice = Price x Quantity)
- Updating dependent fields
- Setting timestamps (CreatedAt, LastChangedAt)
- Business logic-based values (discount based on customer category)
Syntax in Behavior Definition
define behavior for ZI_Travel alias Travel{ // Determination Syntax: determination <MethodName> on <Trigger> { <TriggerCondition>; }
// Trigger: modify (immediate) or save (before DB commit) // TriggerCondition: create, update, field <FieldName>}Example 1: Set Status on Create
// Behavior Definitiondefine behavior for ZI_Travel alias Travelpersistent table ztravel{ create; update;
field ( readonly ) TravelId, Status; field ( readonly ) CreatedBy, CreatedAt;
// Determination: On Create -> Set status to 'O' (Open) determination setInitialStatus on modify { create; }}// Behavior ImplementationCLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS setInitialStatus FOR DETERMINE ON MODIFY IMPORTING keys FOR Travel~setInitialStatus.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD setInitialStatus. " 1. Read current data READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" 2. Only update entities without status MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status CreatedBy CreatedAt ) WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL ) ( %tky = travel-%tky Status = 'O' " Open CreatedBy = cl_abap_context_info=>get_user_name( ) CreatedAt = cl_abap_context_info=>get_system_date( ) ) ) REPORTED DATA(update_reported). ENDMETHOD.
ENDCLASS.Flow:
User: CREATE Travel with Description = 'Business Trip' |Framework: Create in Memory |Determination: setInitialStatus -> Status = 'O' -> CreatedBy = sy-uname -> CreatedAt = today |(Validations are executed) |Framework: INSERT INTO ztravelExample 2: Calculate Total Price (on modify)
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ // When BeginDate or EndDate changes -> Recalculate price determination calculateTotalPrice on modify { field BeginDate, EndDate; }}METHOD calculateTotalPrice. " Read data READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TravelId BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel). " Calculate number of days DATA(lv_days) = ls_travel-EndDate - ls_travel-BeginDate + 1.
" Base price per day (e.g., from customizing) DATA(lv_price_per_day) = 100. " EUR
" Total price DATA(lv_total) = lv_days * lv_price_per_day.
" Discount for longer trips IF lv_days > 7. lv_total = lv_total * '0.90'. " 10% discount ENDIF.
" Set value MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TotalPrice ) WITH VALUE #( ( %tky = ls_travel-%tky TotalPrice = lv_total ) ). ENDLOOP.ENDMETHOD.Trigger examples:
" Execute on CREATEdetermination setDefaults on modify { create; }
" Execute on UPDATEdetermination recalculate on modify { update; }
" On CREATE and UPDATEdetermination calculate on modify { create; update; }
" Only when specific fields changedetermination updateDependentFields on modify { field Price, Quantity, Discount;}
" On SAVE (just before DB commit)determination finalizeData on save { create; update; }Example 3: Set Dependent Fields (on save)
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ // Just before Save: Set approval fields determination setApprovalData on save { update; }}METHOD setApprovalData. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ApprovedBy ApprovedAt ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ApprovedBy ApprovedAt ) WITH VALUE #( FOR travel IN lt_travel WHERE ( Status = 'A' AND ApprovedBy IS INITIAL ) ( %tky = travel-%tky ApprovedBy = cl_abap_context_info=>get_user_name( ) ApprovedAt = cl_abap_context_info=>get_system_date( ) ) ) REPORTED DATA(update_reported).ENDMETHOD.Validations: Checking Business Rules
When to Use Validations?
Perfect for:
- Checking required fields
- Date logic (end date after begin)
- Range checks (discount <= 100%)
- Checking foreign keys (does customer exist?)
- Complex business rules
NOT for:
- Setting values (-> use Determination!)
- Calculations (-> use Determination!)
- Only logging warnings (-> OK, but not as errors)
Syntax in Behavior Definition
define behavior for ZI_Travel alias Travel{ // Validation Syntax: validation <MethodName> on save { <TriggerCondition>; }
// Trigger: ALWAYS "on save" (just before DB commit) // TriggerCondition: create, update, field <FieldName>}Example 1: Check Date Logic
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ create; update;
// Validation: On Save -> Check dates validation validateDates on save { field BeginDate, EndDate; }}// Behavior ImplementationCLASS lhc_travel IMPLEMENTATION.
METHOD validateDates. " 1. Read data READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TravelId BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" 2. Check each entity LOOP AT lt_travel INTO DATA(ls_travel).
" Rule 1: EndDate must be after BeginDate IF ls_travel-EndDate < ls_travel-BeginDate.
" Fill FAILED (technical error) APPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on ) TO failed-travel.
" Fill REPORTED (error message for UI) APPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |End date ({ ls_travel-EndDate DATE = USER }) | && |must be after begin date ({ ls_travel-BeginDate DATE = USER })| ) ) TO reported-travel. ENDIF.
" Rule 2: BeginDate must not be in the past IF ls_travel-BeginDate < cl_abap_context_info=>get_system_date( ).
APPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Travel begin date must not be in the past' ) ) TO reported-travel. ENDIF.
ENDLOOP. ENDMETHOD.
ENDCLASS.What happens on error?
User: UPDATE Travel, EndDate = '20250101', BeginDate = '20250115' |(Determinations are executed) |Validation: validateDates -> Check: EndDate < BeginDate? -> YES! -> FAILED-travel filled -> REPORTED-travel filled |Framework: ROLLBACK (no INSERT/UPDATE) |UI: Display error messageExample 2: Check Foreign Key
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ validation validateCustomer on save { field CustomerId; }}METHOD validateCustomer. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( CustomerId ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" Collect all CustomerIds DATA(lt_customer_ids) = VALUE string_table( FOR travel IN lt_travel ( travel-CustomerId ) ).
" Check if customers exist (via Released API) SELECT Customer FROM I_Customer FOR ALL ENTRIES IN @lt_customer_ids WHERE Customer = @lt_customer_ids-table_line INTO TABLE @DATA(lt_valid_customers).
" Error for non-existing customers LOOP AT lt_travel INTO DATA(ls_travel). IF NOT line_exists( lt_valid_customers[ table_line = ls_travel-CustomerId ] ).
APPEND VALUE #( %tky = ls_travel-%tky %element-CustomerId = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-CustomerId = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Customer { ls_travel-CustomerId } does not exist| ) ) TO reported-travel.
ENDIF. ENDLOOP.ENDMETHOD.Example 3: Complex Business Rule
// Validation: Discount only for premium customersvalidation validateDiscount on save { field Discount, CustomerId;}METHOD validateDiscount. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( CustomerId Discount TotalPrice ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" Load customer categories SELECT Customer, CustomerClassification FROM I_Customer FOR ALL ENTRIES IN @lt_travel WHERE Customer = @lt_travel-CustomerId INTO TABLE @DATA(lt_customers).
LOOP AT lt_travel INTO DATA(ls_travel).
" Rule: > 10% discount only for premium customers (Category 'A') IF ls_travel-Discount > 10.
DATA(ls_customer) = VALUE #( lt_customers[ Customer = ls_travel-CustomerId ] OPTIONAL ).
IF ls_customer-CustomerClassification <> 'A'.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Discount over 10% only for premium customers (current: { ls_customer-CustomerClassification })| ) ) TO reported-travel.
ENDIF. ENDIF.
" Rule 2: Discount max 50% of total price IF ls_travel-Discount > ( ls_travel-TotalPrice / 2 ).
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Discount must not exceed 50% of the total price' ) ) TO reported-travel.
ENDIF.
ENDLOOP.ENDMETHOD.Timing: on modify vs on save
on modify (Determination only!)
determination calculate on modify { create; update; }When executed:
- Immediately after CREATE/UPDATE operation
- BEFORE Validation runs
- Can be executed multiple times (on each modify)
Use Cases:
- Set default values immediately
- Calculate fields that are checked in Validation
- User feedback (field is filled immediately in UI)
on save (Determination & Validation!)
determination finalize on save { update; }validation validateData on save { field Status; }When executed:
- Just before DB commit
- AFTER all on modify Determinations
- Only once per save cycle
Use Cases for Determination:
- Final calculations (e.g., checksums)
- Audit fields (LastChangedBy, LastChangedAt)
- Number assignment from DB number ranges
Use Cases for Validation:
- Check business rules
- Ensure consistency
- Validate foreign keys
Execution Order
User Action: CREATE/UPDATE | v+---------------------------------------+| 1. DETERMINATION on modify (create) | <- Immediate+---------------+-----------------------+ | v+---------------------------------------+| 2. DETERMINATION on modify (update) | <- On each update+---------------+-----------------------+ | v+---------------------------------------+| User: Call COMMIT ENTITIES |+---------------+-----------------------+ | v+---------------------------------------+| 3. DETERMINATION on save | <- Just before DB+---------------+-----------------------+ | v+---------------------------------------+| 4. VALIDATION on save | <- Checks+---------------+-----------------------+ | +- Error? -> ROLLBACK, User sees error message | v Success+---------------------------------------+| 5. INSERT/UPDATE in Database | <- Persistent!+---------------------------------------+Example with both:
define behavior for ZI_Travel alias Travel{ create; update;
// Step 1: Set status immediately determination setInitialStatus on modify { create; }
// Step 2: Recalculate when field changes determination calculateTotal on modify { field BeginDate, EndDate; }
// Step 3: Final values before save determination setApprovalData on save { update; }
// Step 4: Validations validation validateDates on save { field BeginDate, EndDate; } validation validateCustomer on save { field CustomerId; }}Designing Error Messages Correctly
Severity Levels
" ERROR: Blocks Save%msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'End date must be after begin date')
" WARNING: Allows Save, shows warning%msg = new_message_with_text( severity = if_abap_behv_message=>severity-warning text = 'Travel is very short (< 3 days)')
" INFO: Information only%msg = new_message_with_text( severity = if_abap_behv_message=>severity-information text = 'Discount was automatically applied')
" SUCCESS: Positive confirmation%msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = 'Travel successfully approved')Message with Message Class
" Instead of hardcoded text -> Use message classAPPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on %msg = new_message( id = 'ZTRAVEL_MSG' " Message Class (SE91) number = '001' " Message Number severity = if_abap_behv_message=>severity-error v1 = ls_travel-BeginDate " Placeholder &1 v2 = ls_travel-EndDate " Placeholder &2 )) TO reported-travel.
" In SE91: Message ZTRAVEL_MSG/001" Text: "End date &2 must be after begin date &1"Mark Multiple Fields
" Error affects BeginDate AND EndDateAPPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on %element-EndDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Travel period invalid' )) TO reported-travel.
" UI marks BOTH fields redBest Practices
DO: Determinations
" Set values, don't checkMETHOD setDefaults. MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status Currency ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'O' Currency = 'EUR' ) ).ENDMETHOD.
" on modify for user feedbackdetermination calculatePrice on modify { field Quantity, UnitPrice; }" -> User sees new price immediately
" on save for final valuesdetermination generateDocumentNumber on save { create; }" -> Number assigned only on final saveDO: Validations
" Only check, don't modifyMETHOD validateAmount. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Amount ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel) WHERE Amount <= 0. APPEND VALUE #( %tky = ls_travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = ls_travel-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Amount must be greater than 0' ) ) TO reported-travel. ENDLOOP.ENDMETHOD.
" Performance: Bulk read instead of loopSELECT Customer FROM I_Customer FOR ALL ENTRIES IN @lt_travel WHERE Customer = @lt_travel-CustomerId INTO TABLE @DATA(lt_valid).
" Meaningful error messagestext = |Customer { ls_travel-CustomerId } does not exist in the system|" Instead of: "Validation failed"DON’T
" Do NOT modify in ValidationMETHOD validateData. " WRONG: Validation must NOT modify! MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH ... " -> Can lead to inconsistencies!ENDMETHOD.
" Do NOT check and abort in DeterminationMETHOD setDefaults. IF ls_travel-Amount <= 0. " WRONG: Determination should not validate! APPEND ... TO failed-travel. " Use Validation! ENDIF.ENDMETHOD.
" Do NOT use on modify for DB accessdetermination getCustomerData on modify { field CustomerId; }" -> Every time field changes = DB call" -> Better: on save (only once before commit)
" Do NOT use generic error messagestext = 'Error' " Not helpfultext = 'Validation failed' " What's the problem?" -> Be specific: "End date must be after begin date"Testing
CLASS ltc_determinations DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_env TYPE REF TO if_cds_test_environment. METHODS: setup, teardown, test_set_initial_status FOR TESTING, test_calculate_total FOR TESTING.ENDCLASS.
CLASS ltc_determinations IMPLEMENTATION.
METHOD setup. mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ). ENDMETHOD.
METHOD test_set_initial_status. " Arrange: Create travel without status MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( AgencyId CustomerId ) WITH VALUE #( ( %cid = 'T1' AgencyId = '001' CustomerId = '042' ) ) MAPPED DATA(mapped).
COMMIT ENTITIES.
" Act: Determination should have set status READ ENTITIES OF zi_travel ENTITY Travel FIELDS ( Status ) WITH VALUE #( ( %cid = 'T1' ) ) RESULT DATA(lt_travel).
" Assert: Status = 'O' cl_abap_unit_assert=>assert_equals( exp = 'O' act = lt_travel[ 1 ]-Status msg = 'Determination should set status to O' ). ENDMETHOD.
METHOD test_calculate_total. " Test for calculation determination " ... (similar to above) ENDMETHOD.
METHOD teardown. ROLLBACK ENTITIES. mo_env->destroy( ). ENDMETHOD.
ENDCLASS.
CLASS ltc_validations DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_env TYPE REF TO if_cds_test_environment. METHODS: setup, test_validate_dates_error FOR TESTING, test_validate_dates_success FOR TESTING.ENDCLASS.
CLASS ltc_validations IMPLEMENTATION.
METHOD setup. mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ). ENDMETHOD.
METHOD test_validate_dates_error. " Arrange: EndDate < BeginDate MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( BeginDate EndDate ) WITH VALUE #( ( %cid = 'T1' BeginDate = '20250615' EndDate = '20250601' ) ) " WRONG! FAILED DATA(failed).
COMMIT ENTITIES RESPONSE OF zi_travel FAILED DATA(commit_failed) REPORTED DATA(commit_reported).
" Assert: Error expected cl_abap_unit_assert=>assert_not_initial( act = commit_failed-travel msg = 'Validation should report error' ).
" Check message cl_abap_unit_assert=>assert_bound( act = commit_reported-travel[ 1 ]-%msg msg = 'Error message should be present' ). ENDMETHOD.
METHOD test_validate_dates_success. " Arrange: Correct data MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( BeginDate EndDate ) WITH VALUE #( ( %cid = 'T1' BeginDate = '20250601' EndDate = '20250615' ) ) FAILED DATA(failed).
COMMIT ENTITIES RESPONSE OF zi_travel FAILED DATA(commit_failed).
" Assert: No error cl_abap_unit_assert=>assert_initial( act = commit_failed-travel msg = 'Validation should succeed' ). ENDMETHOD.
ENDCLASS.Important Notes / Best Practices
- Determination = Setting, Validation = Checking: Never mix them!
- Mind the timing:
on modifyfor immediate feedback,on savefor final operations - Validation must NOT modify: Only read and fill FAILED/REPORTED
- Performance: Bulk operations instead of loops with individual DB calls
- Meaningful messages: Describe specifically what is wrong
- Mark %element: Shows user exactly which field has the problem
- IN LOCAL MODE: Always use in behavior implementations
- Severity correct: ERROR blocks, WARNING allows save
- Use message class: Instead of hardcoded texts -> enables translation
- Order: Determinations -> Validations -> DB Save
- Test: Write unit tests for EVERY determination and validation
- Collect errors: Report all errors at once, don’t abort at the first one
Further Resources
- RAP Basics: /en/rap-basics/
- EML Guide: /en/eml-entity-manipulation-language/
- Test Doubles: /en/test-doubles-mocking/
- RAP Managed vs Unmanaged: /en/rap-managed-vs-unmanaged/