Source:
<code>public Object doPost(ExecutionRequest xreq) { OperationResource op = (OperationResource) getTarget(); String opId = op.getId(); if (!srv.accept(opId, op.isChain(), request)) { return ResponseHelper.notFound(); } String executionId = UUID.randomUUID().toString(); OperationContext opCtx = xreq.createContext(request, response, null); opCtx.setCallback(new OperationCallback() { @Override public void onChainEnter(OperationType chain) { } @Override public void onChainExit() { setCompleted(executionId); } @Override public void onOperationEnter(OperationContext context, OperationType type, InvokableMethod method, Map<String, Object> params) { enterMethod(executionId, method); } @Override public void onOperationExit(Object output) { setOutput(executionId, (Serializable) output); } @Override public OperationException onError(OperationException error) { <START> setError(executionId, ExceptionHelper.unwrapException(error).getMessage()); <END> return error; } }); String repoName = session.getRepositoryName(); NuxeoPrincipal principal = session.getPrincipal(); new Thread(() -> { TransactionHelper.runInTransaction(() -> { LoginComponent.pushPrincipal(principal); try { CoreSession s = CoreInstance.getCoreSession(repoName, principal); opCtx.setCoreSession(s); service.run(opCtx, opId, xreq.getParams()); } catch (OperationException e) { setError(executionId, ExceptionHelper.unwrapException(e).getMessage()); } finally { LoginComponent.popPrincipal(); } }); }, String.format("Nuxeo-AsyncOperation-%s", executionId)).start(); try { String statusURL = String.format("%s%s/%s/%s", ctx.getServerURL(), getPath(), executionId, STATUS_PATH); return Response.status(HttpServletResponse.SC_ACCEPTED).location(new URI(statusURL)).build(); } catch (URISyntaxException e) { throw new NuxeoException(e); } }</code><technical_language>I added a new method setError(String, Throwable) handle unwrapException logic (and deprecate existing setError method anymore)</technical_language>

Target:
public Object doPost(ExecutionRequest xreq) { OperationResource op = (OperationResource) getTarget(); String opId = op.getId(); if (!srv.accept(opId, op.isChain(), request)) { return ResponseHelper.notFound(); } String executionId = UUID.randomUUID().toString(); OperationContext opCtx = xreq.createContext(request, response, null); opCtx.setCallback(new OperationCallback() { @Override public void onChainEnter(OperationType chain) { } @Override public void onChainExit() { setCompleted(executionId); } @Override public void onOperationEnter(OperationContext context, OperationType type, InvokableMethod method, Map<String, Object> params) { enterMethod(executionId, method); } @Override public void onOperationExit(Object output) { setOutput(executionId, (Serializable) output); } @Override public OperationException onError(OperationException error) { setError(executionId, error); return error; } }); String repoName = session.getRepositoryName(); NuxeoPrincipal principal = session.getPrincipal(); new Thread(() -> { TransactionHelper.runInTransaction(() -> { LoginComponent.pushPrincipal(principal); try { CoreSession s = CoreInstance.getCoreSession(repoName, principal); opCtx.setCoreSession(s); service.run(opCtx, opId, xreq.getParams()); } catch (OperationException e) { setError(executionId, e); } finally { LoginComponent.popPrincipal(); } }); }, String.format("Nuxeo-AsyncOperation-%s", executionId)).start(); try { String statusURL = String.format("%s%s/%s/%s", ctx.getServerURL(), getPath(), executionId, STATUS_PATH); return Response.status(HttpServletResponse.SC_ACCEPTED).location(new URI(statusURL)).build(); } catch (URISyntaxException e) { throw new NuxeoException(e); } }