Staff Software Engineer · TELUS Digital

I design enterprise platforms and ship production AI systems.

14+ years building software across finance, healthcare, travel and digital products — from large-scale Java systems to governed LLM applications.

Brașov, Romania · open to staff/principal engineering, architecture, technical leadership and international consulting.

Portrait of Vasile Borș
Vasile Borș · Architecture · Enterprise Java · AEM · AI
14+ years engineering
Enterprise Java & architecture14+ years across finance, healthcare, travel and digital products — from large-scale platforms to long-lived modernization.
Production AI systemsGoverned LLM applications: retrieval, evaluation, content governance and cost engineering in regulated environments.
AEM & digital experienceContent-led enterprise platforms, authoring systems and performance for client-facing products.
Stories from production

Different systems.
Same engineering mindset.

Four projects where architecture, governance and the unglamorous details decided the outcome. Each opens to the full story, with the diagram or demo that explains it.

AI · Financial Chatbot

A financial chatbot that could only give Legal-approved answers

Generative AI could not be trusted with a regulated client, so every answer the bot could serve was pre-approved — while it still had to understand, clarify and guide.

Generative AIRAGOpenAILangChainEmbeddingsAEM CloudReact
5×more usage than site search
6%of chats needed a human

Figures from the TELUS Digital case study.

A few days after earning my first Generative AI certification, a colleague mentioned a client looking for an AI chatbot for its website. The requirements were unusually demanding: responses had to be controlled, rich website content had to appear inside answers, business rules were strict, and conversation flows needed to be guided.

Existing products did not fit. While the team researched the market, I started a proof of concept using public content from the client's website. At the same time, frontend colleagues began building the chatbot interface from the design team's work.

Within two weeks the POC validated the client's main requirements. It also showed that chatbot responses could include reusable AEM components — images, teasers and buttons — making the experience scalable and aligned with the existing website platform.

The more we learned about the client's situation, the clearer the real challenge became. This was a financial company. Prompting could reduce hallucinations in GPT-3.5, but it could not remove the risk. A chatbot generating its own wording was not acceptable.

So the core idea changed: the chatbot would not freely generate answers. Every answer it could deliver had to be approved by the client's Legal team. Yet it still had to understand what users meant, retain context, ask clarifying questions and guide them to the correct approved answer.

The initial direction came from the POC and became a broader team implementation, with contributions across data science, frontend and software engineering. We used RAG to retrieve candidate answers from a database built from the client's existing content. An LLM then evaluated which retrieved answer best fit the user's message and the conversation context.

Plain RAG was not enough. Different financial products often had similar information, so the embedding space contained significant overlap. We built validation scripts to understand that problem, then refined the approach with contextual query rewriting, multiple question phrasings for every approved answer, disambiguation flags, a context agent that switched to pre-approved clarifying questions, and dataset validation tools with visual reports for the client.

The context agent could use LLM calls, database lookups and live APIs requiring real-time verification. It acted like an interviewer when a question could not be safely resolved immediately, moving the user through controlled decision-tree-like flows.

The solution ran as a microservice connected to AEM, where the interface and response rendering were implemented. I led the chatbot work from the original concept through the overall solution design, while the implementation evolved through the team's shared technical work and validation efforts.

A second challenge was making thousands of approved questions, answers, flows and response components manageable for content authors. I conceived and implemented the AEM integration and content-management approach, representing the data and relationships through AEM Content Fragments.

Direct editing of that data could easily lead to duplicates or outdated content — an important legal risk. To solve it, I worked with the frontend and backend teams on a custom author dashboard with intelligent search for similar content, chatbot simulations after changes, full dataset evaluations, and versioned promotion between environments including rollback and tracking.

The chatbot also needed a safe way to involve a person. For cases involving private account information or questions it could not satisfy, I built an integration with the client's existing call-center tools. When agents were available, users could transition to a human agent in the same interface, and the agent received the previous conversation and a short summary.

The project supported the launch of a new brand created from the merger of two companies. At a time when customers had many questions about their accounts, the chatbot was used five times more than site search, and only 6% of conversations required human escalation.

The lesson for me was that a good AI solution in a regulated environment is not defined by how freely it generates text. It is defined by how well it combines approved content, contextual understanding, validation, controlled flows and a clear route to human support.

See the decision path

A compact version of the conversation and the logic behind each answer. Pick an account type to follow how an approved response is selected.

Digital experience · Brightline

Making server-rendered components come alive with React hydration

AEM rendered the page; React made it interactive. The hard part was making the two agree — exactly.

AEM Cloud ServiceReactAtomic DesignHtmlCompresserJavaSling
0cumulative layout shift
SSR + CSRrendering together

The challenge

On the Brightline project, web performance was everything. Our stack: AEM as the CMS and React for the front end. AEM handled the server-side rendering, React handled everything interactive on the client.

We built the component library with Atomic Design — small base components (atoms) composed into bigger ones (molecules, organisms). The result was a page that rendered as perfectly styled HTML from the server, looking great even with JavaScript disabled. But then came the hard question: how do you take those server-rendered components and make them dynamic without the page shifting around? No CLS, no flicker, no re-render from scratch.

The answer was React hydration: React scans the DOM, finds every component, and attaches its React counterpart to the HTML that is already there — no DOM replacement, the server's markup stays untouched.

The catch

React's hydration has one iron rule: the HTML it generates must match the existing markup exactly. Whitespace. Comments. Attribute order. Everything. And AEM's HTML never matched React's — different spacing, attributes in different orders, comments scattered everywhere. Here is how I made them agree:

  1. Attribute order. Easier than it sounds. Because of Atomic Design, fixing the attributes on a single Link or Button atom fixed them in every organism that used it. One fix, applied everywhere.
  2. HTML compression. We ran the entire page through HtmlCompresser as a final delivery filter — configurable per path and extension via regex — stripping all whitespace and comments (removeSurroundingSpaces, removeInterTagSpaces, removeMultiSpaces).
  3. Protecting author content. AEM is a CMS — authors write content, and some components accept rich text with hand-formatted spacing and empty lines. Aggressive compression destroyed that formatting. So we overrode HtmlCompresser to honor custom marker comments and process only what is inside them minimally, then strip the markers:
<!--skip-compress-start-->
<span>Not compressed <b>text</b> with     more spaces</span>
<span>Keep on new line</span>
<!--skip-compress-end-->

The result

It worked. SSR and React rendering ran together perfectly, with zero CLS. The only price: writing SSR HTML became an exacting craft. So we made it a rule — never write the SSR markup first. Build the React component, copy the HTML from the browser, then adapt it with the necessary Sling logic. When the browser's output is your source of truth, hydration stops being a gamble.

Build automation · Kronsoft

A nightly SNAPSHOT pipeline for a 50-module monolith

Every night all modules rebuild in dependency order, so by morning the whole team starts from a trustworthy integrated build.

Java 8JEEGradleJenkinsGitJira APIMavenSonarQube
~50modules integrated nightly
2–5 hovernight build window

The setting

I joined the team while it was completing the migration of a large legacy application from Ant to Gradle. The application was a distributed pharmacy management platform — roughly 50 modules plus a separate integration module that assembled the actual product.

It was a desktop application that talked to a wide range of services and devices, and exposed services of its own: a point-of-sale interface for cashiers, invoicing, prescription preparation, communication with printers and receipt devices, and integrations with other pharmacies and government services. In practice, it did everything a pharmacy needed in a single system.

The team was distributed across several countries, and the codebase was split so that small groups or individual developers owned specific modules. Work followed an Agile Scrum process: each owner built and tested their own modules locally, swapping the updated .jar files directly from the IDE. If you needed a module someone else had changed, you checked which version was required, updated that dependency's version in your local project, and tested.

Given the size of the application, waiting for a full integration build was not an option — so developers built only the modules they needed and dropped the resulting jars into their local application to test or debug.

This worked, and the team had largely grown used to it. But it carried a serious weakness: integrated testing. Release cycles were not always synchronized between teams, and urgent bug-fix patches and out-of-band releases made it worse. When modules are chained together in a hierarchy with many transitive dependencies, every developer's environment can be perfectly healthy while the integration build still breaks — some failures at compile time, others as logic defects that unit tests may or may not catch.

The observation

Joining the project with substantial Maven and Gradle experience, my first reaction was: why aren't we using SNAPSHOT versions? The answer was simply historical — the team had come from Ant, and the move to Gradle was still fresh. I had worked on that migration myself, alongside a colleague, so it felt natural to make the new build system earn its keep.

The dilemma was how to produce SNAPSHOTs across so many modules. Each build ran its own checks and a large suite of unit tests, and a single Jenkins build of some modules could take an hour or more. Running everything serially was not viable, and expecting developers to keep transitive versions correct across roughly 50 Gradle files was not realistic either.

So I proposed an idea that the project manager accepted immediately: a custom SNAPSHOT build system implemented in Gradle, running every night on Jenkins to validate integration across all modules — so that by morning, developers could pull a fresh build of the application with everything already integrated.

We did not yet have Jenkins pipelines with the new configuration; everything ran on Linux shell scripts and Gradle commands. The concept was relatively simple — the implementation was not.

Dependency tracking in Jira

To keep the dependencies between modules under control, the team used Jira. Each module had a Jira task carrying its version. Every change belonging to a version was linked to that task, and the relationships between modules were defined through Jira issue links as well — for example, #module-A-001 requires #module-B-003. An integration-Release-XXX task represented a release version.

The approach looked unintuitive at first, but it was genuinely useful: everyone — from developer to QA, PM and product owner — could see the dependency graph. It was harder to read than a visual graph, but it had worked for years.

How the pipeline worked

  1. Build the dependency graph. We first ran a scan to construct the graph of all dependencies — which module depended on which — and validated what could run in parallel and what had to run sequentially.
  2. Detect what changed. A job walked the modules in the predefined order and checked Git for changes since the last build, identifying which modules actually needed to be rebuilt.
  3. Validate links in Jira. Using the Jira API, we verified that versions were linked correctly. Where possible, corrections were applied automatically; where they were not, a warning was written to the final log.
  4. Build in order, sharing versions. Each module was built in sequence. On a successful build, its version was written to a shared snapshot-versions.properties file. Before starting, the next module read that file and updated its dependencies to the latest successfully built versions of every other module — then ran its own Gradle build. This is what kept the whole graph consistent without anyone editing version numbers by hand.
  5. Assemble the integration build. At the end, the integration module was built into an .ear packaging all the updated snapshot jars, together with a report containing all logs and any issues found.

What the team gained

By morning, after two to five hours of nightly builds, every developer could start from a trustworthy, fully integrated build. QA could test features ahead of time instead of waiting for a release build that was difficult to assemble — release dependencies had to be collected from Jira and pulled together into the integration build by hand.

As a bonus, all reports produced overnight were attached to a Jira snapshot task on the integration release, so any team member could review them without opening Jenkins. The owners of specific modules also received email notifications when one of their dependencies failed, based on their subscription settings.

The result was a significant improvement in how the team worked and in product quality: fewer defects surfaced late in QA and UAT, less friction in local development, and far less of the stress that comes from "it works on my machine" — the problem where a change passes local testing but fails in QA when the final release is assembled.

Freelance · Web app

A terrain-aware antenna link simulator

A retailer needed a button instead of the ITU-R P.530 math. I added what most simulators ignored: the terrain between the two towers.

HTML / CSS / JSGoogle Maps APIGoogle Elevation APIITU-R P.530
5★client review
2015freelance project

In 2015, during my freelance years on Elance, I worked with a client who ran an antenna and telecommunications store. He knew the hardware well — dishes, feedlines and amplifiers — but needed help turning the engineering calculations behind antenna links into something customers could actually use.

The request sounded simple: add a page where someone could choose two locations and see whether an antenna link might work.

The calculation was based on ITU-R P.530 guidance and link-budget concepts such as free-space loss, rain attenuation, fade margin, Fresnel zones and received signal level. But while working through the formulas, I found that the most important problem was not just the math. It was the terrain between the two antennas.

Distance can be calculated easily. Line of sight cannot. A hill, ridge or obstruction along the route can affect the signal and the Fresnel zone, even when the antenna locations themselves look suitable. That became the central idea of the tool: it should use the actual elevation profile between the two selected points, not treat the terrain as flat.

I integrated Google Maps into the calculation form. A user could select point A and point B, and the map drew a geodesic line between them. The tool then used the Google Elevation API to retrieve the ground profile along that path.

With that information, the simulator could calculate and present:

  • Great-circle distance and azimuth
  • Elevation along the path
  • Free-space loss and atmospheric attenuation
  • Received signal level and fade margin
  • Rain-rate availability for horizontal and vertical polarization
  • Fresnel-zone radius and antenna tilt

The goal was not to make customers read ITU-R P.530 or understand every formula. It was to give them a practical answer: could the connection survive the terrain between the two locations?

Looking back, this project stands out because it connected the physics I enjoyed learning in school with a real use case. It was built with HTML, CSS, JavaScript, Google Maps API, Google Elevation API and ITU-R P.530 link-budget calculations — one of my first freelance projects, and it ended with a five-star review.

Selected work

Different systems.
Same engineering mindset.

A few projects that show the range: digital experience, banking, and long-lived enterprise modernization.

Digital experience

TELUS Digital

Enterprise AEM platforms, production Generative AI and architecture for regulated, client-facing products.

Java 11 · AEM Cloud · React · Maven
Financial services

UniCredit Services

Contract lifecycle, fee calculation and invoicing across a multi-team microservices ecosystem. Led a subteam of 5.

Spring Boot · Spring Batch · Angular · Oracle
Modernization

Kronsoft Development

Senior engineering on a large distributed pharmacy platform — long-lived modernization, build automation and integration for a multi-country team.

Java 8 · JEE · Gradle · Jenkins · SonarQube
Engineering approach

Good engineering isn't about making systems complicated. It's about making complexity manageable.

01

Governance is engineering

In regulated environments, constraints such as Legal-approved content shape the architecture rather than simply limiting it.

02

Modernization beats reinvention

Years of enterprise work have meant moving legacy systems forward while preserving what already works.

03

Production is the real test

A proof of concept is interesting. Operational constraints, evaluation, cost and real users are where engineering becomes real.

Career

14+ years of building.

From Java development to technical leadership, architecture and production AI.

2023 → now

TELUS Digital (WillowTree)

Staff Software Engineer

Enterprise AEM platforms · Generative AI · architecture · client-facing technical leadership
2022 → 2023

Maark

Senior AEM Developer / Technical Consultant

Brightline · AEM Cloud · component architecture · React hydration · performance
2021 → 2022

Technology Reply

Technical Team Lead / Senior Java Developer

UniCredit · banking microservices · contract lifecycle · invoicing
2017 → 2021

Kronsoft Development

IT Consultant / Senior Software Engineer

Healthcare & pharmacy · legacy modernization · nightly SNAPSHOT build automation
2012 → 2017

Consulting / Pentalog / Makro-Mikro

Java Developer → Senior Consultant

Healthcare · smart infrastructure · travel e-commerce · HR platforms · freelance web tools
Technology landscape

Depth where it matters.

Production experience first. Technologies are shown with context rather than as a keyword wall.

Java14+ years · core
Spring10+ years · core
AEM Cloud4 years · production
Generative AIproduction systems
TypeScriptproduction · full-stack
Reactproduction · hydration
Angularproduction · several versions
Node.js / NestJSproduction
PostgreSQLproduction
Oracleenterprise · PL/SQL
Microservicesenterprise architecture
Modernizationlegacy platform migration
About

Engineer. Architect.
Consultant.

Staff Software Engineer with 14+ years across finance, healthcare, travel and digital products. Specialized in enterprise architecture, Java/Spring, AEM, modernization and Generative AI.

Education

BSc · Applied Informatics

Transilvania University of Brașov · 2009–2012

Certifications

Certifications

  • Generative AI with LLMs DeepLearning.AI + AWS · Coursera · Sep 2023
    Verify ↗
  • Claude Code 101 Anthropic Education · Jun 2026
    Verify ↗
Languages

Romanian · English

Romanian native. English professional working proficiency. Russian and French at beginner level.

Let's talk

Open to Staff / Principal Software Engineer roles

Also open to architecture, technical leadership and high-level international consulting engagements.