Inline Declarations: Compact and Readable ABAP Code

Category
ABAP
Published
Author
Johannes

Inline declarations have been available since ABAP 7.40 and revolutionize how we declare variables and field symbols. Instead of writing separate DATA and FIELD-SYMBOLS statements, you can declare variables directly where you need them.

What Are Inline Declarations?

Inline declarations enable declaring variables and field symbols directly in the statement that assigns a value to them. The compiler automatically derives the type.

SyntaxDescriptionAvailable Since
DATA(var)Variable with type inferenceABAP 7.40
FIELD-SYMBOL(<fs>)Field symbol with type inferenceABAP 7.40
FINAL(var)Immutable variableABAP 7.57

Advantages of Inline Declarations

  • Fewer lines of code: No separate DATA statements needed
  • Automatic typing: The compiler knows the correct type
  • Better readability: Declaration and usage at the same location
  • Reduced error sources: No type mismatches between declaration and usage

DATA() Inline Declaration

Basics

The simplest form of inline declaration uses DATA():

CLASS zcl_inline_basics DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_basics IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Classic declaration (old)
DATA lv_text_old TYPE string.
lv_text_old = 'Hello World'.
" Inline declaration (new)
DATA(lv_text_new) = 'Hello World'.
" Type is automatically inferred
DATA(lv_number) = 42. " Type: i (integer)
DATA(lv_decimal) = '3.14'. " Type: string (Caution!)
DATA(lv_packed) = CONV decfloat34( '3.14' ). " Type: decfloat34
out->write( |Text: { lv_text_new }| ).
out->write( |Number: { lv_number }| ).
out->write( |Decimal: { lv_packed }| ).
" Method return directly into variable
DATA(lv_timestamp) = cl_abap_context_info=>get_system_date( ).
out->write( |Date: { lv_timestamp }| ).
ENDMETHOD.
ENDCLASS.

Understanding Type Inference

The compiler derives the type from the right-hand expression:

" String literals -> csequence (char-like)
DATA(lv_char) = 'ABC'. " Type: char3
" String Template -> string
DATA(lv_string) = |ABC|. " Type: string
" Numeric literals
DATA(lv_int) = 100. " Type: i
DATA(lv_float) = '1.5'. " Type: string (NOT p or f!)
" Explicit conversion for correct types
DATA(lv_amount) = CONV netwr( '1234.56' ). " Type: netwr
DATA(lv_quantity) = CONV menge( 10 ). " Type: menge

FIELD-SYMBOL() Inline Declaration

Field symbols can also be declared inline:

CLASS zcl_inline_fieldsymbols DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_fieldsymbols IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Create internal table
DATA(lt_flights) = VALUE ty_t_flight(
( carrid = 'LH' connid = '400' price = 500 )
( carrid = 'LH' connid = '401' price = 600 )
( carrid = 'AA' connid = '100' price = 800 )
).
" Classic LOOP with field symbol (old)
" FIELD-SYMBOLS <fs_flight_old> TYPE ty_s_flight.
" LOOP AT lt_flights ASSIGNING <fs_flight_old>.
" ENDLOOP.
" Inline declaration in LOOP (new)
LOOP AT lt_flights ASSIGNING FIELD-SYMBOL(<fs_flight>).
" Direct write access possible
<fs_flight>-price = <fs_flight>-price * '1.1'.
out->write( |{ <fs_flight>-carrid } { <fs_flight>-connid }: { <fs_flight>-price }| ).
ENDLOOP.
" Inline FIELD-SYMBOL with READ TABLE
READ TABLE lt_flights ASSIGNING FIELD-SYMBOL(<fs_found>)
WITH KEY carrid = 'AA'.
IF sy-subrc = 0.
out->write( |Found: { <fs_found>-carrid } { <fs_found>-connid }| ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Inline Declaration in LOOP

The most common use of inline declarations is in LOOP constructs:

CLASS zcl_inline_loop DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_order,
order_id TYPE sysuuid_x16,
customer_id TYPE i,
amount TYPE p DECIMALS 2,
status TYPE c LENGTH 1,
END OF ty_s_order.
TYPES ty_t_orders TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_inline_loop IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_orders) = VALUE ty_t_orders(
( customer_id = 1 amount = '100.00' status = 'N' )
( customer_id = 1 amount = '250.00' status = 'P' )
( customer_id = 2 amount = '175.50' status = 'C' )
).
" LOOP with INTO for copy (read-only)
LOOP AT lt_orders INTO DATA(ls_order).
out->write( |Order for customer { ls_order-customer_id }: { ls_order-amount }| ).
ENDLOOP.
" LOOP with ASSIGNING for reference (write access)
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<fs_order>)
WHERE status = 'N'.
<fs_order>-status = 'P'. " Direct update
out->write( |Status changed for amount { <fs_order>-amount }| ).
ENDLOOP.
" LOOP with INDEX for row number
LOOP AT lt_orders INTO DATA(ls_ord) INDEX INTO DATA(lv_index).
out->write( |Row { lv_index }: { ls_ord-amount }| ).
ENDLOOP.
" LOOP AT GROUP BY for grouping
LOOP AT lt_orders INTO DATA(ls_customer_order)
GROUP BY ( customer_id = ls_customer_order-customer_id )
INTO DATA(lt_group).
DATA(lv_total) = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR <order> IN GROUP lt_group
NEXT sum = sum + <order>-amount
).
out->write( |Customer { lt_group-customer_id } Total: { lv_total }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Inline Declaration in READ TABLE

READ TABLE benefits particularly from inline declarations:

CLASS zcl_inline_read_table DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_product,
product_id TYPE c LENGTH 10,
name TYPE string,
price TYPE p DECIMALS 2,
stock TYPE i,
END OF ty_s_product.
TYPES ty_t_products TYPE SORTED TABLE OF ty_s_product
WITH UNIQUE KEY product_id.
ENDCLASS.
CLASS zcl_inline_read_table IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_products) = VALUE ty_t_products(
( product_id = 'PROD001' name = 'Laptop' price = '999.99' stock = 50 )
( product_id = 'PROD002' name = 'Mouse' price = '29.99' stock = 200 )
( product_id = 'PROD003' name = 'Keyboard' price = '79.99' stock = 100 )
).
" READ TABLE with INTO and inline declaration
READ TABLE lt_products INTO DATA(ls_product)
WITH TABLE KEY product_id = 'PROD001'.
IF sy-subrc = 0.
out->write( |Product: { ls_product-name } - { ls_product-price } EUR| ).
ENDIF.
" READ TABLE with ASSIGNING and inline declaration
READ TABLE lt_products ASSIGNING FIELD-SYMBOL(<fs_product>)
WITH TABLE KEY product_id = 'PROD002'.
IF sy-subrc = 0.
<fs_product>-stock = <fs_product>-stock - 1. " Reduce stock
out->write( |New stock for { <fs_product>-name }: { <fs_product>-stock }| ).
ENDIF.
" READ TABLE with REFERENCE INTO
READ TABLE lt_products REFERENCE INTO DATA(lr_product)
WITH TABLE KEY product_id = 'PROD003'.
IF sy-subrc = 0.
out->write( |Reference: { lr_product->name }| ).
ENDIF.
" Table expression with DEFAULT (without sy-subrc)
DATA(ls_found) = VALUE #(
lt_products[ product_id = 'PROD001' ]
DEFAULT VALUE #( name = 'Not found' )
).
out->write( |Found: { ls_found-name }| ).
" Optional: Check Line Exists
IF line_exists( lt_products[ product_id = 'PROD999' ] ).
out->write( 'Product exists' ).
ELSE.
out->write( 'Product not found' ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Inline Declaration in SELECT

Inline declarations in SELECT statements make code particularly compact:

CLASS zcl_inline_select DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_select IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" SELECT into internal table with inline declaration
SELECT carrid, connid, fldate, price
FROM sflight
WHERE carrid = 'LH'
INTO TABLE @DATA(lt_flights).
IF sy-subrc = 0.
out->write( |{ lines( lt_flights ) } flights found| ).
ENDIF.
" SELECT SINGLE with inline declaration
SELECT SINGLE carrid, connid, cityfrom, cityto
FROM spfli
WHERE carrid = 'LH' AND connid = '400'
INTO @DATA(ls_connection).
IF sy-subrc = 0.
out->write( |Route: { ls_connection-cityfrom } -> { ls_connection-cityto }| ).
ENDIF.
" SELECT with LOOP and inline declaration
SELECT carrid, carrname
FROM scarr
INTO @DATA(ls_carrier).
out->write( |Airline: { ls_carrier-carrid } - { ls_carrier-carrname }| ).
ENDSELECT.
" Aggregation with inline declaration
SELECT carrid, SUM( price ) AS total_revenue
FROM sflight
GROUP BY carrid
INTO TABLE @DATA(lt_revenue).
LOOP AT lt_revenue INTO DATA(ls_rev).
out->write( |{ ls_rev-carrid }: { ls_rev-total_revenue }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

FINAL() for Immutable Variables

Since ABAP 7.57, FINAL() is available for constants with type inference:

CLASS zcl_inline_final DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_final IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" FINAL for immutable values
FINAL(lc_max_items) = 100.
FINAL(lc_api_endpoint) = |https://api.example.com/v1|.
" Compiler error if you try to change FINAL variable:
" lc_max_items = 200. " <- This would cause an error!
out->write( |Max Items: { lc_max_items }| ).
out->write( |API: { lc_api_endpoint }| ).
" FINAL in LOOP (each iteration is new, so allowed)
DATA(lt_values) = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
LOOP AT lt_values INTO FINAL(lv_value).
out->write( |Value: { lv_value }| ).
" lv_value = 'X'. " <- Error: FINAL variable cannot be changed
ENDLOOP.
" FINAL for method return values
FINAL(lv_today) = cl_abap_context_info=>get_system_date( ).
out->write( |Today: { lv_today }| ).
ENDMETHOD.
ENDCLASS.

Best Practices and Readability

When to Use Inline Declarations

SituationRecommendation
LOOP, READ TABLE, SELECTAlways use inline
Method return valuesInline when type is clear
Complex typesPrefer explicit declaration
Reused variablesExplicit declaration at the beginning

Avoiding Anti-Patterns

CLASS zcl_inline_best_practices DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_best_practices IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" BAD: Multiple declarations in the same scope
" DATA(lv_result) = calculate_a( ).
" DATA(lv_result) = calculate_b( ). " Compile error!
" GOOD: One variable, multiple assignments
DATA lv_result TYPE i.
lv_result = 10.
lv_result = 20.
" BAD: Unclear type from literals
DATA(lv_unclear) = '123'. " Is this a string or a number?
" GOOD: Explicit conversion
DATA(lv_number) = CONV i( '123' ).
" BAD: Lines too long due to inline
" DATA(ls_very_long) = VALUE ty_s_complex( field1 = 'a' field2 = 'b' ... ).
" GOOD: Split for complex structures
DATA(ls_order) = VALUE ty_s_order(
order_id = cl_system_uuid=>create_uuid_x16_static( )
customer_id = 1
amount = '100.00'
status = 'N'
).
out->write( |Result: { lv_result }| ).
out->write( |Number: { lv_number }| ).
ENDMETHOD.
ENDCLASS.

Type Inference vs. Explicit Types

" Type inference is ideal for:
" - Local, short-lived variables
" - Method return values
" - LOOP/READ TABLE results
" Explicit types are better for:
" - Interface parameters
" - Class member variables
" - When the type should be documented
" - ABAP-typical currency/quantity fields
" Example: Explicit type for currency field
DATA lv_amount TYPE netwr.
lv_amount = '1234.56'.
" vs. Inline (type must still be correct)
DATA(lv_amount_inline) = CONV netwr( '1234.56' ).

Conclusion

Inline declarations make ABAP code more compact and readable. The key points:

  • DATA() for normal variables with type inference
  • FIELD-SYMBOL() for field symbols in LOOP and READ TABLE
  • FINAL() for immutable values (from 7.57)
  • Use CONV for explicit type conversion with literals
  • Use inline declarations in LOOP, READ TABLE, and SELECT
  • For complex or reused variables: classic declaration at method start

Further Reading

  • Modern ABAP Syntax
  • Clean ABAP
  • ABAP SQL Window Functions