String Templates (also called template strings) enable elegant creation of strings with embedded expressions and formatting options. They are delimited with |...| and contain expressions in {...}.
Syntax
|Literal text { expression } more text||{ expression OPTION = value }|Basic Principle
- Text between
|...|is interpreted as a string {...}contains expressions that are evaluated and inserted- Formatting options control the presentation
- Escape sequences for special characters:
\|,\{,\},\\,\n,\r,\t
Examples
1. Simple Interpolation
DATA: lv_name TYPE string VALUE 'Max', lv_age TYPE i VALUE 30.
" Classic with CONCATENATEDATA: lv_text TYPE string.CONCATENATE 'Hello ' lv_name ', you are ' lv_age ' years old.' INTO lv_text.
" Modern with String TemplateDATA(lv_text2) = |Hello { lv_name }, you are { lv_age } years old.|.
WRITE: / lv_text2. " Hello Max, you are 30 years old.2. Expressions in Templates
DATA: lv_a TYPE i VALUE 10, lv_b TYPE i VALUE 5.
" Embed calculations directlyDATA(lv_calc) = |{ lv_a } + { lv_b } = { lv_a + lv_b }|.WRITE: / lv_calc. " 10 + 5 = 15
" Method callsDATA(lv_upper) = |Name: { to_upper( lv_name ) }|.WRITE: / lv_upper. " Name: MAX
" Conditional expressionsDATA(lv_status) = |Status: { COND #( WHEN lv_age >= 18 THEN 'Adult' ELSE 'Minor' ) }|.3. Number Formatting (WIDTH, ALIGN, PAD)
DATA: lv_num TYPE i VALUE 42.
" Minimum width with WIDTHDATA(lv_w) = |Number: { lv_num WIDTH = 10 }|.WRITE: / lv_w. " Number: 42
" Alignment with ALIGNDATA(lv_left) = |[{ lv_num WIDTH = 10 ALIGN = LEFT }]|.DATA(lv_right) = |[{ lv_num WIDTH = 10 ALIGN = RIGHT }]|.DATA(lv_center) = |[{ lv_num WIDTH = 10 ALIGN = CENTER }]|.
WRITE: / lv_left. " [42 ]WRITE: / lv_right. " [ 42]WRITE: / lv_center. " [ 42 ]
" Fill character with PADDATA(lv_padded) = |{ lv_num WIDTH = 6 ALIGN = RIGHT PAD = '0' }|.WRITE: / lv_padded. " 0000424. Formatting Decimal Numbers (DECIMALS, SIGN)
DATA: lv_amount TYPE p DECIMALS 2 VALUE '-1234.56'.
" Specify decimal placesDATA(lv_d1) = |Amount: { lv_amount DECIMALS = 2 }|.WRITE: / lv_d1. " Amount: -1234.56
" Sign positionDATA(lv_sign_left) = |{ lv_amount SIGN = LEFT }|. " -1234.56DATA(lv_sign_right) = |{ lv_amount SIGN = RIGHT }|. " 1234.56-DATA(lv_sign_leftplus) = |{ lv_amount SIGN = LEFTPLUS }|. " -1234.56 (+ for positive)
" Thousands separatorDATA: lv_big TYPE p DECIMALS 2 VALUE '1234567.89'.DATA(lv_sep) = |{ lv_big NUMBER = USER }|. " User format (e.g., 1,234,567.89)DATA(lv_raw) = |{ lv_big NUMBER = RAW }|. " Without formatting5. Date Formatting (DATE)
DATA: lv_date TYPE d VALUE '20241115'.
" Various date formatsDATA(lv_d_raw) = |{ lv_date DATE = RAW }|. " 20241115DATA(lv_d_iso) = |{ lv_date DATE = ISO }|. " 2024-11-15DATA(lv_d_user) = |{ lv_date DATE = USER }|. " User formatDATA(lv_d_env) = |{ lv_date DATE = ENVIRONMENT }|.
WRITE: / 'RAW:', lv_d_raw.WRITE: / 'ISO:', lv_d_iso.WRITE: / 'USER:', lv_d_user.6. Time Formatting (TIME)
DATA: lv_time TYPE t VALUE '143025'.
" Various time formatsDATA(lv_t_raw) = |{ lv_time TIME = RAW }|. " 143025DATA(lv_t_iso) = |{ lv_time TIME = ISO }|. " 14:30:25DATA(lv_t_user) = |{ lv_time TIME = USER }|. " User format
WRITE: / 'Time:', lv_t_iso.7. Timestamp Formatting (TIMESTAMP)
DATA: lv_ts TYPE timestamp.GET TIME STAMP FIELD lv_ts.
DATA(lv_ts_iso) = |{ lv_ts TIMESTAMP = ISO }|.DATA(lv_ts_space) = |{ lv_ts TIMESTAMP = SPACE }|.DATA(lv_ts_user) = |{ lv_ts TIMESTAMP = USER }|.
WRITE: / 'Timestamp:', lv_ts_iso.8. Alpha Conversion (ALPHA)
DATA: lv_matnr TYPE string VALUE '000000000000012345'.
" Alpha OUT: Remove leading zerosDATA(lv_without_zeros) = |{ lv_matnr ALPHA = OUT }|.WRITE: / lv_without_zeros. " 12345
" Alpha IN: Add leading zerosDATA: lv_short TYPE string VALUE '12345'.DATA(lv_with_zeros) = |{ lv_short ALPHA = IN WIDTH = 18 }|.WRITE: / lv_with_zeros. " 0000000000000123459. Case Conversion (CASE)
DATA: lv_text TYPE string VALUE 'Hello World'.
DATA(lv_upper) = |{ lv_text CASE = UPPER }|. " HELLO WORLDDATA(lv_lower) = |{ lv_text CASE = LOWER }|. " hello worldDATA(lv_raw) = |{ lv_text CASE = RAW }|. " Hello World
WRITE: / lv_upper.WRITE: / lv_lower.10. Escape Sequences
" Escape special charactersDATA(lv_pipe) = |Pipe: \| and curly braces: \{ \}|.DATA(lv_newline) = |Line 1\nLine 2|.DATA(lv_tab) = |Column1\tColumn2|.DATA(lv_backsl) = |Path: C:\\Folder\\File|.
WRITE: / lv_pipe.WRITE: / lv_newline.WRITE: / lv_tab.11. Multi-line String Templates
" Strings can span multiple linesDATA(lv_multi) = |This is a | && |multi-line | && |string.|.
" Or with \n for actual line breaksDATA(lv_lines) = |Line 1\n| && |Line 2\n| && |Line 3|.
cl_demo_output=>display( lv_lines ).12. Combination with COND and SWITCH
DATA: lv_status TYPE i VALUE 2.
DATA(lv_msg) = |Status: { SWITCH string( lv_status WHEN 1 THEN 'New' WHEN 2 THEN 'In Progress' WHEN 3 THEN 'Completed' ELSE 'Unknown') }|.
WRITE: / lv_msg. " Status: In Progress13. Embedding Structure Fields
TYPES: BEGIN OF ty_customer, id TYPE i, name TYPE string, city TYPE string, END OF ty_customer.
DATA: ls_customer TYPE ty_customer.
ls_customer = VALUE #( id = 1001 name = 'Miller GmbH' city = 'Berlin' ).
DATA(lv_info) = |Customer { ls_customer-id }: { ls_customer-name } from { ls_customer-city }|.
WRITE: / lv_info. " Customer 1001: Miller GmbH from Berlin14. Table Access in Templates
DATA: lt_names TYPE TABLE OF string.
lt_names = VALUE #( ( `Anna` ) ( `Bernd` ) ( `Clara` ) ).
" Direct table accessDATA(lv_first) = |First entry: { lt_names[ 1 ] }|.WRITE: / lv_first. " First entry: Anna
" With OPTIONAL for safe accessDATA(lv_safe) = |Entry: { VALUE #( lt_names[ 999 ] OPTIONAL ) }|.15. String Templates in Loops
LOOP AT lt_names INTO DATA(lv_name). DATA(lv_line) = |{ sy-tabix }. { lv_name }|. WRITE: / lv_line.ENDLOOP.
" Output:" 1. Anna" 2. Bernd" 3. Clara16. String Templates for SQL
" Building dynamic SQL (Caution: SQL Injection!)DATA: lv_field TYPE string VALUE 'CARRID', lv_value TYPE string VALUE 'LH'.
" Better: Use parameters instead of string concatenation for SQL17. Combining Formatting Options
DATA: lv_price TYPE p DECIMALS 2 VALUE '1234.50'.
DATA(lv_formatted) = |Price: { lv_price DECIMALS = 2 SIGN = LEFT WIDTH = 12 ALIGN = RIGHT PAD = ' '} EUR|.
WRITE: / lv_formatted. " Price: 1234.50 EUR18. XSD Format for Boolean
DATA: lv_flag TYPE abap_bool VALUE abap_true.
" XSD format: true/false instead of X/''DATA(lv_xsd) = |Active: { xsdbool( lv_flag ) }|.WRITE: / lv_xsd. " Active: X
" Custom representationDATA(lv_custom) = |Active: { COND #( WHEN lv_flag = abap_true THEN 'Yes' ELSE 'No' ) }|.Formatting Options Overview
| Option | Values | Description |
|---|---|---|
WIDTH | Number | Minimum width |
ALIGN | LEFT, RIGHT, CENTER | Alignment |
PAD | Character | Fill character |
CASE | UPPER, LOWER, RAW | Case conversion |
SIGN | LEFT, RIGHT, LEFTPLUS, RIGHTPLUS | Sign position |
DECIMALS | Number | Decimal places |
NUMBER | RAW, USER, ENVIRONMENT | Number format |
DATE | RAW, ISO, USER, ENVIRONMENT | Date format |
TIME | RAW, ISO, USER, ENVIRONMENT | Time format |
TIMESTAMP | ISO, SPACE, USER | Timestamp format |
ALPHA | IN, OUT, RAW | Alpha conversion |
CURRENCY | Currency code | Currency format |
COUNTRY | Country code | Country format |
Comparison: Classic vs. String Templates
" === CLASSIC ===DATA: lv_result TYPE string.
CONCATENATE 'Customer' ls_customer-id ':' ls_customer-name INTO lv_result SEPARATED BY space.
" Or with &&lv_result = 'Customer ' && ls_customer-id && ': ' && ls_customer-name.
" === MODERN WITH STRING TEMPLATES ===DATA(lv_result2) = |Customer { ls_customer-id }: { ls_customer-name }|.Best Practices
- String templates begin and end with
|(pipe character). - Expressions in
{...}are evaluated at runtime. - Escape sequences:
\|for pipe,\{\}for braces,\nfor line break. - Formatting options often make
WRITE TOand conversion routines obsolete. ALPHA = OUTis practical for material numbers without leading zeros.DATE = ISOprovides standardized format (YYYY-MM-DD).- Combine with
CONDandSWITCHfor conditional text. - String templates are performant – prefer them over
CONCATENATE. - Caution with dynamic SQL – use parameters instead of string interpolation.
NUMBER = USERrespects user settings for decimal/thousands separators.