mohd-faraz

_blogs

// blogs / 20260909.md

Dev Log: September 09 Wrap-up

2026-09-09
#java#async#debugging#backend

Overview

Spent the day deep in the guts of our ticket automation logic. It was one of those days where a few 'simple' fixes turned into a deep dive into how our background services talk to each other, especially around authentication and async execution.

What I Worked On

Squashing a template interpolation bug

I ran into a frustrating issue where the background automation flow was receiving blank data. It turns out the downstream template was expecting a single message string containing all the ticket details, but we were sending individual fields. If the keys don't match exactly what the template engine expects, it just renders nothing.

I had to bundle the ID, subject, and description into a formatted string so the prompt template could actually see them. It feels a bit like double-encoding, but it got the job done:

// Bundling fields into a single string for the template engine
String composedMessage = String.format(
    "{ \"id\": \"%s\", \"subject\": \"%s\", \"body\": \"%s\" }",
    data.getId(), 
    data.getSubject(), 
    data.getContent()
);

Map<String, Object> payload = new HashMap<>();
payload.put("message", composedMessage);

Making the triage flow non-blocking

There was no reason for the main thread to wait for the triage automation to finish before returning a response to the user. I wrapped the trigger logic in CompletableFuture.runAsync. Now, even if the workflow service is feeling sluggish, it won't hang the entire ticket update process. The UI feels noticeably snappier now since we aren't waiting on those external network hops.

Solving the 'Server-to-Server' header mystery

This was the trickiest part of the day. We have an allowlist on our internal workflow service that checks the Origin and Referer headers. Because this specific call is server-to-server (backend calling backend), those headers don't exist naturally like they would in a browser.

I had to manually reconstruct the origin using the domain context from the original request. It’s a bit of a hop-and-skip—pulling the domain from the caller's context and injecting it into the new RestClient headers—but it ensures the downstream security filters don't reject the call as 'untrusted.'

Housekeeping on data resources

I also spent some time updating our internal resource models. We were missing token_type and id fields in a few DTOs used for flow execution. It’s standard plumbing, but skipping it was causing some serialization issues during the auth handshake.

Wrapping Up

Most of the heavy lifting for the ticket assignment automation is done. Tomorrow I’ll probably spend some time monitoring the logs to make sure the async blocks aren't swallowing any weird edge-case exceptions. Catch you then.