UTL_HTTP.REQUEST returns only a body. For real web service calls you need more: headers on the request, the status code and headers of the response, a body of any length, authentication, and redirects. BEGIN_REQUEST and GET_RESPONSE give that control.
Code for This Guide
The main examples are in the examples/pkg-files-network folder of the Oracle Database 26ai code repository on GitHub, each with its output. They use NIMBUS, the sample schema of a fictional airline, which you can install with the scripts in the setup/nimbus folder.
They come from Oracle Database 26ai SQL and PL/SQL Book.
Syntax
req := utl_http.begin_request(url, method => 'GET'); utl_http.set_header(req, name, value); utl_http.set_authentication(req, username, password); resp := utl_http.get_response(req); -- resp.status_code, resp.reason_phrase utl_http.get_header(resp, n, name_out, value_out); utl_http.read_line(resp, line_out, remove_crlf => true); -- or read_text utl_http.end_response(resp);
Reading past the end of the body raises UTL_HTTP.END_OF_BODY. Always call END_RESPONSE, or the connection stays open.
The examples call the local test server, setup/netlab/server.mjs in the repository.
Status, Headers, and Body
Example:
declare
v_req utl_http.req;
v_resp utl_http.resp;
v_name varchar2(256);
v_value varchar2(1024);
v_line varchar2(1024);
begin
v_req := utl_http.begin_request('http://host.docker.internal:8099/notices');
utl_http.set_header(v_req, 'User-Agent', 'Nimbus-PLSQL/1.0');
v_resp := utl_http.get_response(v_req);
dbms_output.put_line('status: ' || v_resp.status_code || ' ' || v_resp.reason_phrase);
for i in 1 .. utl_http.get_header_count(v_resp) loop
utl_http.get_header(v_resp, i, v_name, v_value);
if v_name like 'Content%' then
dbms_output.put_line(v_name || ': ' || v_value);
end if;
end loop;
begin
loop
utl_http.read_line(v_resp, v_line, remove_crlf => true);
dbms_output.put_line('> ' || v_line);
end loop;
exception
when utl_http.end_of_body then
utl_http.end_response(v_resp);
end;
end;
/Output:
status: 200 OK Content-Type: text/plain; charset=utf-8 > Gate B12: boarding from 21:30 > Lounge: open until 23:00 > Wi-Fi: NimbusGuest PL/SQL procedure successfully completed.
The request sends a User-Agent header. The response has status 200 OK and a plain text content type, and READ_LINE returns the body line by line until END_OF_BODY, where the handler ends the response.
Authentication, Redirects, and Errors
Example:
declare
v_req utl_http.req;
v_resp utl_http.resp;
v_text varchar2(4000);
begin
v_req := utl_http.begin_request('http://host.docker.internal:8099/reports/daily');
utl_http.set_authentication(v_req, 'ops', 'ops-demo'); -- basic authentication
v_resp := utl_http.get_response(v_req);
utl_http.read_text(v_resp, v_text);
utl_http.end_response(v_resp);
dbms_output.put_line(v_resp.status_code || ': ' || v_text);
v_req := utl_http.begin_request('http://host.docker.internal:8099/old-status');
v_resp := utl_http.get_response(v_req); -- follows the redirect
utl_http.read_text(v_resp, v_text);
utl_http.end_response(v_resp);
dbms_output.put_line('after redirect: ' || v_text);
utl_http.set_detailed_excp_support(true);
v_text := utl_http.request('http://host.docker.internal:8099/reports/daily'); -- 401 body
dbms_output.put_line('without credentials: ' || v_text);
v_text := utl_http.request('http://host.docker.internal:8100/'); -- no ACL for port 8100
exception
when others then
dbms_output.put_line(sqlerrm);
end;
/Output:
200: {"date":"2026-03-15","flights":31,"on_time_pct":87.1}
after redirect: {"flight":"NM150","status":"ON TIME","gate":"B12","departs":"2026-03-15T22:10:00+13:00"}
without credentials: {"error":"authentication required"}
ORA-24247: network access denied by access control list (ACL)
PL/SQL procedure successfully completed.- SET_AUTHENTICATION sends basic authentication, and the protected report returns 200.
- /old-status redirects, and GET_RESPONSE follows it to the flight status.
- Without credentials, the server answers 401 with an error body.
- Port 8100 has no access control entry, so the call fails with ORA-24247.
Things to Know
- Check STATUS_CODE before using the body: 4xx and 5xx responses do not raise errors.
- Keep passwords out of code: store them in a wallet and use SET_AUTHENTICATION_FROM_WALLET.
- SET_TRANSFER_TIMEOUT limits how long a slow server can keep your session waiting.
Related Guides
Conclusion
BEGIN_REQUEST, GET_RESPONSE, and the read procedures give full control over HTTP calls: headers, status codes, bodies of any length, authentication, and redirects. Check the status, read until END_OF_BODY, and always end the response.
