RAP Determinations and Validations: The Complete Guide

Category
ABAP-Statements
Published
Author
Johannes

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 |
+-----------------------+
AspectDeterminationValidation
PurposeSet/calculate valuesCheck values
Timingon modify or on saveon save
ChangesMay modify entityRead-only, no changes
ErrorsNo direct errorsFills FAILED & REPORTED
ExampleSet status to ‘O’Check end date after begin
OrderFirst (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 Definition
define behavior for ZI_Travel alias Travel
persistent 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 Implementation
CLASS 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 ztravel

Example 2: Calculate Total Price (on modify)

// Behavior Definition
define 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 CREATE
determination setDefaults on modify { create; }
" Execute on UPDATE
determination recalculate on modify { update; }
" On CREATE and UPDATE
determination calculate on modify { create; update; }
" Only when specific fields change
determination 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 Definition
define 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 Definition
define behavior for ZI_Travel alias Travel
{
create;
update;
// Validation: On Save -> Check dates
validation validateDates on save {
field BeginDate, EndDate;
}
}
// Behavior Implementation
CLASS 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 message

Example 2: Check Foreign Key

// Behavior Definition
define 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 customers
validation 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 class
APPEND 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 EndDate
APPEND 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 red

Best Practices

DO: Determinations

" Set values, don't check
METHOD 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 feedback
determination calculatePrice on modify { field Quantity, UnitPrice; }
" -> User sees new price immediately
" on save for final values
determination generateDocumentNumber on save { create; }
" -> Number assigned only on final save

DO: Validations

" Only check, don't modify
METHOD 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 loop
SELECT Customer FROM I_Customer
FOR ALL ENTRIES IN @lt_travel
WHERE Customer = @lt_travel-CustomerId
INTO TABLE @DATA(lt_valid).
" Meaningful error messages
text = |Customer { ls_travel-CustomerId } does not exist in the system|
" Instead of: "Validation failed"

DON’T

" Do NOT modify in Validation
METHOD 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 Determination
METHOD 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 access
determination getCustomerData on modify { field CustomerId; }
" -> Every time field changes = DB call
" -> Better: on save (only once before commit)
" Do NOT use generic error messages
text = 'Error' " Not helpful
text = '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 modify for immediate feedback, on save for 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