Introduction
Many business applications require unique numbers, for example, to complete the keys of data records. In order to get numbers from an interval, a number range object must be defined which can contain different properties. Standard Number ranges are typically maintained via customizing in each system (Test and Production). However, the process for custom Number Range Objects in the S/4HANA Cloud Public Edition is different.
These custom objects must be created in the development environment, configured in the customizing client, and then transported through the landscape. This blog outlines that end-to-end process, including a frustrating short dump I encountered and the surprisingly simple configuration change that solved it.
Part 1: The Developer’s Task – Defining the Blueprint in ADT
Unlike the on-premises world, the process for a custom number range begins in the developer’s toolkit: ABAP Development Tools (ADT). Here, you create the definition or the blueprint of your number range object.
In your ABAP package, right-click and choose New -> Other ABAP Repository Object.Search for “Number Range Object”, give it a name and a description.Assign it to a Workbench Transport.In the object editor, the most important property is the Number Length Domain. It’s a best practice to create a custom domain for this to define the length and type. For my ZPO_RUN_ID, I used a domain of type NUMC(10).Save and activate the object.
The system now knows what a ZPO_RUN_ID is, but it has no actual number intervals defined.
Part 2: The Customizer’s Task – Setting Intervals in Fiori
This is where we move to the Fiori Launchpad, specifically in the Customizing Client.
Log in to your Customizing client and open the “Manage Number Range Intervals” Fiori app.Search for your newly created object, ZPO_RUN_ID.Navigate to the “Number Ranges” tab and click “Create”. Here you’ll define your interval, for example, 01 with its lower and upper limits.
Part 3: The Transport
The system needs a Customizing Transport to record this change.
In a new tab, open the “Export Customizing Transports” Fiori app.
Create a new “Customizing Request” and give it a description. It’s a good practice to set this as your “Default” request using the “Change Category” button.
Go back to the Number Range app and save your interval again. This time, it will find your open request and save the change successfully.
Now you have two transports: the Workbench TR from ADT with the object definition, and the Customizing TR from Fiori with the interval data. The Workbench TR should be a prerequisite for the Customizing TR when you import them.
Part 4: The Payoff – Using the Number Range in RAP
With everything configured, it was time to use it in my ABAP RAP application. The correct, released API is the class CL_NUMBERRANGE_RUNTIME.
I had two main options for assigning an ID based on the business requirement:
An early numbering determination, which runs before the save phase.A determination on save, which runs during the final save transaction.
I initially chose early numbering determination. My earlynumbering_create method was clean and used the correct class.
METHOD earlynumbering_create.
DATA: latest_num TYPE cl_numberrange_runtime=>nr_number.
DATA(All_entities) = entities.
DELETE All_entities WHERE RunId IS NOT INITIAL.
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
nr_range_nr = ’01’
object = ‘ZPO_RUN_ID’
quantity = CONV #( lines( all_entities ) )
IMPORTING
number = latest_num
returncode = DATA(code)
returned_quantity = DATA(return_qty) ).
CATCH cx_nr_object_not_found INTO DATA(lx_obj_not_found).
CATCH cx_number_ranges INTO DATA(lx_ranges_error).
LOOP AT All_entities INTO DATA(entity_line).
APPEND VALUE #( %cid = entity_line-%cid
%key = entity_line-%key ) TO failed-run.
APPEND VALUE #( %cid = entity_line-%cid
%key = entity_line-%key
%msg = lx_ranges_error ) TO reported-run.
ENDLOOP.
EXIT.
ENDTRY.
DATA(curr_num) = latest_num – return_qty.
LOOP AT All_entities INTO entity_line.
curr_num = curr_num + 1 .
APPEND VALUE #( %cid = entity_line-%cid
RunUuid = curr_num ) TO mapped-run.
ENDLOOP.
ENDMETHOD.
I ran the app, created an entry, clicked “Save,” and was met with a short dump: ABAP Runtime error ‘BEHAVIOR_ILLEGAL_STATEMENT’.
The “Aha!” Moment: The COMMIT WORK Conflict and the Simple Fix
My code was correct. The class was correct. The configuration was correct. So, what was wrong?
The problem was my number range was not buffered.
After a long debugging session, I discovered that when cl_numberrange_runtime is called for a non-buffered number range, it must perform an immediate database UPDATE and its own COMMIT WORK to guarantee the number is reserved. The RAP framework, in its strictly controlled Save phase, saw this independent COMMIT as a violation of its own transaction and triggered the dump to prevent data inconsistency.
The solution wasn’t in my ABAP code at all. It was in the configuration.
Enabling buffering solves this transactional conflict. Here’s why:
With buffering, the class gets the next number from the application server’s memory, not directly from the database.Getting a number from memory requires no immediate database update and, crucially, no conflicting COMMIT WORK.
I edited the Properties of my ZPO_RUN_ID object and changed the Buffering type to “Main Memory Buffering” with a size of 10.
With that one configuration change, everything worked perfectly. My determination on save now executes without any issues.
Final Thoughts
In the ABAP Cloud, writing correct code is only half the battle. We also have to deeply understand and respect the strict transactional model of the RAP framework. A seemingly simple configuration choice, like buffering, can be the key to resolving what looks like a complex runtime error.
If you ever face an unexpected BEHAVIOR_ILLEGAL_STATEMENT dump, consider what your code might be doing that could be trying to interfere with the framework’s unit of work.
IntroductionMany business applications require unique numbers, for example, to complete the keys of data records. In order to get numbers from an interval, a number range object must be defined which can contain different properties. Standard Number ranges are typically maintained via customizing in each system (Test and Production). However, the process for custom Number Range Objects in the S/4HANA Cloud Public Edition is different.These custom objects must be created in the development environment, configured in the customizing client, and then transported through the landscape. This blog outlines that end-to-end process, including a frustrating short dump I encountered and the surprisingly simple configuration change that solved it.Part 1: The Developer’s Task – Defining the Blueprint in ADTUnlike the on-premises world, the process for a custom number range begins in the developer’s toolkit: ABAP Development Tools (ADT). Here, you create the definition or the blueprint of your number range object.In your ABAP package, right-click and choose New -> Other ABAP Repository Object.Search for “Number Range Object”, give it a name and a description.Assign it to a Workbench Transport.In the object editor, the most important property is the Number Length Domain. It’s a best practice to create a custom domain for this to define the length and type. For my ZPO_RUN_ID, I used a domain of type NUMC(10).Save and activate the object.The system now knows what a ZPO_RUN_ID is, but it has no actual number intervals defined.Part 2: The Customizer’s Task – Setting Intervals in FioriThis is where we move to the Fiori Launchpad, specifically in the Customizing Client.Log in to your Customizing client and open the “Manage Number Range Intervals” Fiori app.Search for your newly created object, ZPO_RUN_ID.Navigate to the “Number Ranges” tab and click “Create”. Here you’ll define your interval, for example, 01 with its lower and upper limits.Part 3: The Transport The system needs a Customizing Transport to record this change.In a new tab, open the “Export Customizing Transports” Fiori app.Create a new “Customizing Request” and give it a description. It’s a good practice to set this as your “Default” request using the “Change Category” button.Go back to the Number Range app and save your interval again. This time, it will find your open request and save the change successfully.Now you have two transports: the Workbench TR from ADT with the object definition, and the Customizing TR from Fiori with the interval data. The Workbench TR should be a prerequisite for the Customizing TR when you import them.Part 4: The Payoff – Using the Number Range in RAPWith everything configured, it was time to use it in my ABAP RAP application. The correct, released API is the class CL_NUMBERRANGE_RUNTIME.I had two main options for assigning an ID based on the business requirement:An early numbering determination, which runs before the save phase.A determination on save, which runs during the final save transaction.I initially chose early numbering determination. My earlynumbering_create method was clean and used the correct class.METHOD earlynumbering_create.
DATA: latest_num TYPE cl_numberrange_runtime=>nr_number.
DATA(All_entities) = entities.
DELETE All_entities WHERE RunId IS NOT INITIAL.
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
nr_range_nr = ’01’
object = ‘ZPO_RUN_ID’
quantity = CONV #( lines( all_entities ) )
IMPORTING
number = latest_num
returncode = DATA(code)
returned_quantity = DATA(return_qty) ).
CATCH cx_nr_object_not_found INTO DATA(lx_obj_not_found).
CATCH cx_number_ranges INTO DATA(lx_ranges_error).
LOOP AT All_entities INTO DATA(entity_line).
APPEND VALUE #( %cid = entity_line-%cid
%key = entity_line-%key ) TO failed-run.
APPEND VALUE #( %cid = entity_line-%cid
%key = entity_line-%key
%msg = lx_ranges_error ) TO reported-run.
ENDLOOP.
EXIT.
ENDTRY.
DATA(curr_num) = latest_num – return_qty.
LOOP AT All_entities INTO entity_line.
curr_num = curr_num + 1 .
APPEND VALUE #( %cid = entity_line-%cid
RunUuid = curr_num ) TO mapped-run.
ENDLOOP.
ENDMETHOD.I ran the app, created an entry, clicked “Save,” and was met with a short dump: ABAP Runtime error ‘BEHAVIOR_ILLEGAL_STATEMENT’. The “Aha!” Moment: The COMMIT WORK Conflict and the Simple FixMy code was correct. The class was correct. The configuration was correct. So, what was wrong?The problem was my number range was not buffered.After a long debugging session, I discovered that when cl_numberrange_runtime is called for a non-buffered number range, it must perform an immediate database UPDATE and its own COMMIT WORK to guarantee the number is reserved. The RAP framework, in its strictly controlled Save phase, saw this independent COMMIT as a violation of its own transaction and triggered the dump to prevent data inconsistency.The solution wasn’t in my ABAP code at all. It was in the configuration.Enabling buffering solves this transactional conflict. Here’s why:With buffering, the class gets the next number from the application server’s memory, not directly from the database.Getting a number from memory requires no immediate database update and, crucially, no conflicting COMMIT WORK.I edited the Properties of my ZPO_RUN_ID object and changed the Buffering type to “Main Memory Buffering” with a size of 10.With that one configuration change, everything worked perfectly. My determination on save now executes without any issues.Final ThoughtsIn the ABAP Cloud, writing correct code is only half the battle. We also have to deeply understand and respect the strict transactional model of the RAP framework. A seemingly simple configuration choice, like buffering, can be the key to resolving what looks like a complex runtime error.If you ever face an unexpected BEHAVIOR_ILLEGAL_STATEMENT dump, consider what your code might be doing that could be trying to interfere with the framework’s unit of work. Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog