Why apex.server.process Success Fires Even on Errors (and How to Handle It)

Introduction
In my previous post, I shared how to implement bulk update/delete in Interactive Grids using apex.server.process
and JSON payloads.
In that post, I mentioned a common source of confusion:
👉 “I raised an error in my PL/SQL Ajax Callback… so why did my JavaScript success
function still run?”
This post clears up that confusion and shows the right pattern for handling errors with structured JSON.
The Confusion
Let’s imagine you wrote a PL/SQL Ajax Callback like this:
raise_application_error(-20001, 'Select at least one row.');
When you call it from JS:
apex.server.process("PROC", {x01: payload}, {
success: function(pData){ console.log("Success", pData); },
error: function(req, status, err){ console.error("Error", err); }
});
You expected the error handler to run. But instead, the success handler fired (with no useful data). Why?
HTTP Success ≠ Application Success
The key is that apex.server.process
treats the HTTP response status as the measure of success/failure.
If PL/SQL completes normally (no unhandled exception) → APEX sends back an HTTP 200 OK → success callback runs.
If PL/SQL crashes (unhandled exception, ORA- error, invalid process) → APEX returns HTTP 500 → error callback runs.
This means:
✅ Transport-level success (200 OK) → JS success callback always runs.
🚫 Business logic errors (validation, “no rows selected”) → must be handled inside the success callback.
The send_error
Pattern
Instead of raising exceptions for business rules, return structured JSON:
PL/SQL:
procedure send_error(p_msg varchar2) is
begin
apex_json.open_object;
apex_json.write('ok', false);
apex_json.open_array('errors');
apex_json.open_object;
apex_json.write('type','error');
apex_json.write('location','page');
apex_json.write('message', p_msg);
apex_json.close_object;
apex_json.close_array;
apex_json.close_object;
end;
Use it like this:
if l_size = 0 then
send_error('Select at least one row.');
return;
end if;
Client JS:
apex.server.process("PROC", { x01: payload }, {
success: function(pData){
if (pData && pData.ok === false) {
apex.message.showErrors(pData.errors);
return; // stop success path
}
// normal success path
},
error: function(req, status, err){
apex.message.showErrors([{type:'error', location:'page', message:'Server error: ' + err}]);
}
});
When the error
Callback Really Fires
The error:
handler only triggers when the HTTP request fails:
Network issues
Server unreachable
Unhandled PL/SQL exception → HTTP 500
Invalid process name
Visual Recap
200 OK + ok:true → success path
200 OK + ok:false → validation error → showErrors
500 Error → technical failure → error handler
Key Takeaways
apex.server.process
success callback means “HTTP OK,” not “business OK.”For business/validation rules → return structured JSON (
ok:false, errors[]
).For technical crashes → error callback handles HTTP 500/404.
Always separate transport success from application success.
Closing
This small but important distinction helps avoid a lot of head-scratching in APEX apps.
👉 Next up, I’ll share more practical use cases for APEX Collections and how they can power real-world features like carts and wizards.
Subscribe to my newsletter
Read articles from Bhuvnesh Singh Chauhan (bhuvi_champ) directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
