In an interconnected enterprise runtime, SAP is no longer an isolated platform. Whether you need to fetch real-time exchange rates, send invoice payloads to external microservices, or integrate cloud services, communicating over HTTP and parsing JSON or XML has become standard practice for every ABAP developer.
But how do HTTP requests actually work? What are headers and cookies, and how do we manage them? And how can you easily convert ABAP data objects into JSON or XML (and vice versa)?
In this guide, we break these concepts down into simple, practical terms. We will start with an easy-to-understand waiter analogy, examine the role of headers and cookies, and dive straight into ready-to-use code examples utilizing modern ABAP and inline declarations.
1. Deconstructing an HTTP Request (The Waiter Analogy)#
To understand HTTP communication, imagine dining at a restaurant. There are four primary actors:
- The Guest (Client): You (or your ABAP program) wanting to request something.
- The Order (Request): Your message outlining what you want.
- The Waiter (HTTP Client): The messenger carrying your order to the kitchen and bringing the food back.
- The Kitchen (Web Server / API): The backend processing your request and preparing the response.
Core Anatomy of a Request:#
- The URL (Endpoint Address): The street address or table location (e.g.,
https://api.example.com/v1/products).
HTTP Methods (The Active Verbs)#
To define what action we want the server to perform, we use specific HTTP methods. Here is how they map to our restaurant analogy and database operations:
| HTTP Method | Restaurant Analogy | Database Action (CRUD) | Description |
|---|
GET | “Bring me the menu” or “Serve the soup” | Read (Retrieve) | Fetches existing resource details from the server without modifying anything. |
POST | “Place a new custom order” | Create (Insert) | Submits new data payloads to the server to create a brand new resource. |
PUT / PATCH | “Replace my steak with fish” / “Add extra pepper” | Update (Modify) | Overwrites a resource entirely (PUT) or updates specific fields (PATCH). |
DELETE | “Cancel my order” / “Take away the empty plate” | Delete (Remove) | Permanently deletes a specific resource from the server. |
This entire interaction is mapped in the Mermaid sequence diagram below:
sequenceDiagram
autonumber
actor Guest as Guest (ABAP Client)
participant Waiter as Waiter (HTTP Client)
participant Kitchen as Kitchen (Web Server)
Note over Guest,Kitchen: The HTTP Request-Response Lifecycle
Guest->>Waiter: 1. Place order (HTTP Method, URL, Headers, Body)
activate Waiter
Waiter->>Kitchen: 2. Carry order to kitchen (Send Request)
activate Kitchen
Note over Kitchen: Process payload,
Query DB, Compute
Kitchen-->>Waiter: 3. Return prepared dish (HTTP Response: Status 200 + Payload)
deactivate Kitchen
Waiter-->>Guest: 4. Deliver dish to table (Receive & process response)
deactivate Waiter
2. Understanding Headers and Cookies#
When communicating over HTTP, the request and response do not just consist of raw payloads. They also contain metadata: Headers and Cookies.
Headers are key-value pairs sent in both requests and responses. Think of request headers as your “special dining instructions” or “ID verification” when placing an order:
Accept: application/json: Telling the kitchen, “I only understand JSON. Please serve my data format accordingly.”Content-Type: application/json; charset=utf-8: Telling the kitchen, “The payload body I am handing over is written in UTF-8-encoded JSON.”Authorization: Bearer <token>: Your exclusive membership card allowing you access to VIP dining areas.
What are Cookies?#
Cookies are small pieces of stateful data that a server sends to your client via a response header (Set-Cookie). Your client then stores them and automatically attaches them to subsequent outgoing requests (Cookie header).
Think of cookies as a physical cloakroom ticket the restaurant hands you on your first visit:
- On your response, the waiter says: “Keep this ticket locally.” (
Set-Cookie: session_id=XYZ123) - On your next request, you automatically show that ticket back to the waiter: “Remember me? Here is my ticket.” (
Cookie: session_id=XYZ123) - This is crucial for Session Management, keeping track of logged-in states, or preserving user preferences across stateless HTTP calls.
3. Execution of HTTP Requests in ABAP#
In modern ABAP instances (such as SAP S/4HANA Cloud or modern On-Premise systems), we use the IF_WEB_HTTP_CLIENT interface. It is the Clean Core-compliant successor to the legacy CL_HTTP_CLIENT class.
Here is an example demonstrating how to initialize a client, set headers, manage cookies, and retrieve a response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| TRY.
" 1. Instantiate the HTTP Client using the URL Destination
DATA(lo_http_client) = cl_web_http_client_manager=>create_by_http_destination(
cl_http_destination_provider=>create_by_url( 'https://jsonplaceholder.typicode.com/posts' )
).
" 2. Obtain the request configuration API
DATA(lo_request) = lo_http_client->get_http_request( ).
" 3. Setting HTTP Headers
lo_request->set_header_field( i_name = 'Accept' i_value = 'application/json' ).
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
" 4. Managing Cookies
" Manually set a cookie for state tracking/session handling
lo_request->get_cookie_manager( )->set_cookie(
i_name = 'sap-user-context'
i_value = 'language=EN&client=100'
).
" Set request payload (JSON Body)
lo_request->set_text( `{"title": "Clean Core", "body": "Modern ABAP is great!", "userId": 1}` ).
" 5. Execute HTTP Request via POST Method
DATA(lo_response) = lo_http_client->execute( if_web_http_client=>post ).
" 6. Evaluate HTTP Response Metainfo and Payload
DATA(ls_status) = lo_response->get_status( ).
DATA(lv_response_txt) = lo_response->get_text( ).
IF ls_status-code = 201. " Created successfully
cl_demo_output=>write( 'Successfully created new entry!' ).
cl_demo_output=>write( lv_response_txt ).
ELSE.
cl_demo_output=>write( |Error: { ls_status-code } - { ls_status-reason }| ).
ENDIF.
" 7. Terminate connection
lo_http_client->close( ).
CATCH cx_web_http_client_error cx_http_dest_provider_error INTO DATA(lx_error).
cl_demo_output=>write( |Exception caught: { lx_error->get_text( ) }| ).
ENDTRY.
cl_demo_output=>display( ).
|
4. Handling JSON Payloads (The Modern Standard)#
JSON (JavaScript Object Notation) is the de-facto data serialization standard for modern API endpoints. ABAP makes parsing and creating JSON extremely seamless using /UI2/CL_JSON combined with inline data declarations.
JSON Deserialization (JSON to ABAP)#
Let’s parse incoming JSON data directly into an inline-defined ABAP structure without pre-creating global dictionary structures:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| " Raw JSON response
DATA(lv_json_string) = `{ "id": 101, "title": "Modern ABAP is great!", "completed": false }`.
" Define target structure inline on the fly
DATA: BEGIN OF ls_todo,
id TYPE i,
title TYPE string,
completed TYPE abap_bool,
END OF ls_todo.
" De-serialize using camel-case mapping to snake_case automatically
/UI2/CL_JSON=>deserialize(
EXPORTING
json = lv_json_string
pretty_name = /UI2/CL_JSON=>pretty_name-camel_case
CHANGING
data = ls_todo
).
" Display structural attributes
cl_demo_output=>write( |ID: { ls_todo-id }| ).
cl_demo_output=>write( |Title: { ls_todo-title }| ).
cl_demo_output=>write( |Completed: { ls_todo-completed }| ).
cl_demo_output=>display( ).
|
JSON Serialization (ABAP to JSON)#
Generating modern JSON structures out of internal ABAP objects is just as fast:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| " Build data structure inline
DATA(ls_payload) = VALUE #(
id = 999
title = 'Post created via ABAP'
completed = abap_true
).
" Serialize ABAP structure into formatted JSON
DATA(lv_json_output) = /UI2/CL_JSON=>serialize(
data = ls_payload
compress = abap_true
pretty_name = /UI2/CL_JSON=>pretty_name-camel_case
).
cl_demo_output=>write( lv_json_output ).
cl_demo_output=>display( ).
|
5. Handling XML Payloads (The Standard Classic)#
For older legacy platforms, SOAP-based systems, or explicit SAP-to-SAP background messaging, XML is the typical vehicle. We manipulate XML using ABAP’s identity transformation: CALL TRANSFORMATION id.
XML Deserialization (XML to ABAP)#
We want to parse the following hierarchical XML into a structured ABAP record:
1
2
3
4
| <post>
<id>404</id>
<title>XML Parsing</title>
</post>
|
We do this easily using an inline structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| " Raw XML text string
DATA(lv_xml_payload) = `<post><id>404</id><title>XML Parsing</title></post>`.
" Inline defined structure matching the hierarchy
DATA: BEGIN OF ls_post_data,
id TYPE i,
title TYPE string,
END OF ls_post_data.
TRY.
" Parse XML using the system identity (ID) transformation
CALL TRANSFORMATION id
SOURCE xml lv_xml_payload
RESULT post = ls_post_data. " Matches the XML root tag 'post'
cl_demo_output=>write( |Parsed Title: { ls_post_data-title } (ID: { ls_post_data-id })| ).
CATCH cx_transformation_error INTO DATA(lx_xml_err).
cl_demo_output=>write( |Error parsing XML: { lx_xml_err->get_text( ) }| ).
ENDTRY.
cl_demo_output=>display( ).
|
XML Serialization (ABAP to XML)#
To wrap records back into an XML format:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| " Fill an inline structure
DATA(ls_invoice) = VALUE #(
invoice_id = 'INV-5501'
purchaser = 'Wayne Enterprises'
).
DATA lv_xml_output TYPE string.
" Convert structural data to raw XML string
CALL TRANSFORMATION id
SOURCE data = ls_invoice
RESULT xml lv_xml_output.
cl_demo_output=>write( lv_xml_output ).
cl_demo_output=>display( ).
|
Complex XML Structures
When simple ID mapping is insufficient for deeply nested schemas or advanced namespaces, you should create a dedicated Simple Transformation (ST) or XSLT object in Eclipse ADT and trigger it inside your CALL TRANSFORMATION statement.
Summary & Integration Best Practices#
By following these fundamental practices, you write clean, reliable integration logic every time:
- Avoid
CL_HTTP_CLIENT for New Service Configurations: The modern IF_WEB_HTTP_CLIENT client API is secure, decoupled, and Clean-Core compliant. - Utilize Inline Declarations: Declaring data containers, loops, and target records directly in your serialization and deserialization blocks keeps your logical layers clean of redundant SE11 Dictionary overhead.
- Always Wrap in Try-Catch Blocks: Network calls, HTTP timeouts, and corrupted payloads will happen. Wrapping execution steps in defensive
TRY-CATCH blocks keeps your background jobs from crashing on unexpected remote states.
Pro Tip: Wrap your HTTP Requests in a Helper Class#
If you find yourself making HTTP requests in multiple places, writing boilerplate code to handle headers, cookies, query parameters, destinations, and clients gets repetitive and messy. Wrapping these calls into a clean, reusable utility class simplifies your application logic significantly.
Here is a robust helper class ZCL_HTTP_HANDLER that supports modern REST methods (GET, POST, PUT, DELETE), handles query parameter tables, tracks stateful headers/cookies, and manages client lifecycle cleanly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
| CLASS zcl_http_handler DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_name_value,
name TYPE string,
value TYPE string,
END OF ty_name_value,
tt_name_value TYPE STANDARD TABLE OF ty_name_value WITH DEFAULT KEY.
METHODS:
constructor
RAISING
cx_web_http_client_error,
set_header
IMPORTING
iv_name TYPE string
iv_value TYPE string,
set_cookie
IMPORTING
iv_name TYPE string
iv_value TYPE string,
get
IMPORTING
iv_url TYPE string
it_query_params TYPE tt_name_value OPTIONAL
RETURNING
VALUE(rv_response) TYPE string
RAISING
cx_web_http_client_error
cx_http_dest_provider_error,
post
IMPORTING
iv_url TYPE string
it_query_params TYPE tt_name_value OPTIONAL
iv_body TYPE string OPTIONAL
RETURNING
VALUE(rv_response) TYPE string
RAISING
cx_web_http_client_error
cx_http_dest_provider_error,
put
IMPORTING
iv_url TYPE string
it_query_params TYPE tt_name_value OPTIONAL
iv_body TYPE string OPTIONAL
RETURNING
VALUE(rv_response) TYPE string
RAISING
cx_web_http_client_error
cx_http_dest_provider_error,
delete
IMPORTING
iv_url TYPE string
it_query_params TYPE tt_name_value OPTIONAL
RETURNING
VALUE(rv_response) TYPE string
RAISING
cx_web_http_client_error
cx_http_dest_provider_error.
PRIVATE SECTION.
DATA:
mt_headers TYPE tt_name_value,
mt_cookies TYPE tt_name_value.
METHODS:
create_client
IMPORTING
iv_url TYPE string
RETURNING
VALUE(ro_client) TYPE REF TO if_web_http_client
RAISING
cx_web_http_client_error
cx_http_dest_provider_error,
prepare_request
IMPORTING
io_client TYPE REF TO if_web_http_client
it_query_params TYPE tt_name_value OPTIONAL
iv_body TYPE string OPTIONAL
RAISING
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_http_handler IMPLEMENTATION.
METHOD constructor.
" Initialisierung falls notwendig
ENDMETHOD.
METHOD set_header.
DELETE mt_headers WHERE name = iv_name.
APPEND VALUE #( name = iv_name value = iv_value ) TO mt_headers.
ENDMETHOD.
METHOD set_cookie.
DELETE mt_cookies WHERE name = iv_name.
APPEND VALUE #( name = iv_name value = iv_value ) TO mt_cookies.
ENDMETHOD.
METHOD create_client.
DATA(lo_destination) = cl_http_destination_provider=>create_by_url( iv_url ).
ro_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
ENDMETHOD.
METHOD prepare_request.
DATA(lo_request) = io_client->get_http_request( ).
" Header-Felder setzen
LOOP AT mt_headers ASSIGNING FIELD-SYMBOL(<fs_header>).
lo_request->set_header_field(
i_name = <fs_header>-name
i_value = <fs_header>-value
).
ENDLOOP.
" Cookies setzen
LOOP AT mt_cookies ASSIGNING FIELD-SYMBOL(<fs_cookie>).
lo_request->set_cookie(
i_name = <fs_cookie>-name
i_value = <fs_cookie>-value
).
ENDLOOP.
" Query-Parameter setzen
LOOP AT it_query_params ASSIGNING FIELD-SYMBOL(<fs_param>).
lo_request->set_query_parameter(
name = <fs_param>-name
value = <fs_param>-value
).
ENDLOOP.
" Body-Inhalt falls vorhanden setzen
IF iv_body IS NOT INITIAL.
lo_request->set_text( iv_body ).
ENDIF.
ENDMETHOD.
METHOD get.
DATA(lo_client) = create_client( iv_url ).
prepare_request(
io_client = lo_client
it_query_params = it_query_params
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
rv_response = lo_response->get_text( ).
lo_client->close( ).
ENDMETHOD.
METHOD post.
DATA(lo_client) = create_client( iv_url ).
prepare_request(
io_client = lo_client
it_query_params = it_query_params
iv_body = iv_body
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
rv_response = lo_response->get_text( ).
lo_client->close( ).
ENDMETHOD.
METHOD put.
DATA(lo_client) = create_client( iv_url ).
prepare_request(
io_client = lo_client
it_query_params = it_query_params
iv_body = iv_body
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>put ).
rv_response = lo_response->get_text( ).
lo_client->close( ).
ENDMETHOD.
METHOD delete.
DATA(lo_client) = create_client( iv_url ).
prepare_request(
io_client = lo_client
it_query_params = it_query_params
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>delete ).
rv_response = lo_response->get_text( ).
lo_client->close( ).
ENDMETHOD.
ENDCLASS.
|
How to use the ZCL_HTTP_HANDLER Wrapper Class#
Using this wrapper class makes your main business logic short, clean, and extremely readable. Here are practical examples demonstrating how to use it for different HTTP operations:
1. A Simple GET Request with Query Parameters#
Instead of instantiating destinations, clients, and requests manually, you simply call the wrapper class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| TRY.
DATA(lo_http) = NEW zcl_http_handler( ).
" Inline lookup query parameter table
DATA(lt_params) = VALUE zcl_http_handler=>tt_name_value(
( name = 'userId' value = '1' )
).
" Fetch data
DATA(lv_response) = lo_http->get(
iv_url = 'https://jsonplaceholder.typicode.com/posts'
it_query_params = lt_params
).
cl_demo_output=>write( lv_response ).
CATCH cx_web_http_client_error cx_http_dest_provider_error INTO DATA(lx_err).
cl_demo_output=>write( lx_err->get_text( ) ).
ENDTRY.
cl_demo_output=>display( ).
|
2. A Stateful POST Request with Headers and Cookies#
If you need to pass authentication tokens, context headers, or cookies alongside a payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| TRY.
DATA(lo_http) = NEW zcl_http_handler( ).
" Set headers and cookies (which are stored in the object state)
lo_http->set_header( iv_name = 'Authorization' iv_value = 'Bearer s0me-secr3t-asdf-t0k3n' ).
lo_http->set_header( iv_name = 'Content-Type' iv_value = 'application/json' ).
lo_http->set_cookie( iv_name = 'mysessionid' iv_value = '987654321' ).
DATA(lv_json_payload) = `{"service": "active", "status": "completed"}`.
" Execute POST request with state active
DATA(lv_response) = lo_http->post(
iv_url = 'https://api.example.com/v1/status'
iv_body = lv_json_payload
).
cl_demo_output=>write( lv_response ).
CATCH cx_web_http_client_error cx_http_dest_provider_error INTO DATA(lx_err).
cl_demo_output=>write( lx_err->get_text( ) ).
ENDTRY.
cl_demo_output=>display( ).
|