Ferry Suhandri

Senior Software Development Engineer
Make it correct, then make it fast

About Me

Ferry

I'm a Software Engineer with 8+ years of experience, at my best on the hard parts of production systems: keeping data correct under concurrency, speeding up slow endpoints and safely changing legacy code. I fix root causes, from write-skew bugs unnoticed for years to a 60s+ API cut to under 1s. On my own, I rewrote a delivery-routing engine with jsprit, AWS Lambda and Spring WebFlux, and several tools I built became team standards. I keep my code above 90% test coverage and mentor juniors.

Experience

NTT DATA

Sep 2026 - Present

Senior Associate Software Development Engineer

PT. Advotics Teknologi Global

Sep 2019 - Jun 2026

Software Development Engineer

Backend engineer on a multi-tenant supply-chain SaaS serving thousands of client companies (thousands to tens of thousands of transactions per minute; p95 50–300 ms on some services). I took over the Delivery Platform in July 2023 and, after the teams merged in January 2024, the User Identity Platform and Analytics Platform modules as well. Several of my refactors became team standards: request binding instead of manual HttpServletRequest parsing, centralized exception handling with @ControllerAdvice, a builder-based HTTP client, the annotation-based endpoint router, the Thymeleaf templating module for PDFs and HTML emails, and @LegacyTransactional. I held my new code above 90% unit-test coverage (JUnit, Mockito, AssertJ), which the team followed as its standard. Also reviewed code and mentored junior engineers.

Business Transaction Platform

Sep 2019 - Jun 2026

Backend for Advotics' supply-chain modules: Ordering, Invoice, Product Return and Inventory (Spring Boot, Hibernate, SQL), owned together by a team of 3–10 engineers over the years. Maintained the ordering module's per-client behavior design and extended it with new client-specific strategies as clients needed them, without changing the core order flow. Built new APIs with the team's Clean Architecture (Command-pattern use cases, Façade gateways).

Delivery Platform

Jul 2023 - Jun 2026

Rewrote the delivery-plan routing out of the legacy delivery service into a standalone engine. It uses jsprit with custom constraints on AWS Lambda for vehicle routing with multiple vehicles, warehouses, time windows and vehicle skills, and Spring WebFlux for the data-loading API, which I cut from 60s+ to under 1s. Also built the Delivery Trip module.

User Identity Platform

Jan 2024 - Jun 2026

Took over the module after the teams merged in January 2024. Owned sales check-in/check-out tracking, including fixing the bugs behind unreliable travel distances in sales reports, and closing a user-enumeration and email-exposure hole in password reset.

Analytics Platform

Jan 2024 - Jun 2026

Took over the module after the teams merged. Rebuilt the analytics ETL around daily and monthly aggregates, cutting queries from over a minute to under 500 ms, and migrated the message queue from AWS SQS to Redis Streams.

KPI Platform

Jun 2022 - Jun 2023

Enhanced the KPI calculator that tracks sales performance. We built a separate library that calculates KPIs from each client's own formula. I chose QueryDSL as its dynamic query builder, so queries are assembled in Java from each KPI's configuration, which kept the logic composable and readable. Teammates then adopted QueryDSL for their own queries.

PT. Pradipta Jatis Indonesia

Jan 2018 - Aug 2019

Associate Business Technologist

Software engineer at an IT consultancy, assigned to five client projects in 20 months across mutual funds, retail e-commerce, digital vouchers and pharmaceutical analytics. Often joined at short notice: replaced a colleague on Alfacart's back office, supported Ultra Voucher as a newly formed startup, and learned Groovy and Grails on the job to ship a mutual funds app on time. Worked with Java, Spring, MyBatis, PostgreSQL, Groovy/Grails and vanilla JavaScript.

Permodalan Nasional Madani Investment Management

May 2019 - Aug 2019

Built a mutual funds app on a tight deadline in Groovy and Grails, which nobody on the team had used before. We learned the stack while building and launched on time.

Alfacart Back Office

Dec 2018 - Apr 2019

Full-stack work on back-office apps (Java backend, vanilla JavaScript frontend). Implemented discount periods that must not overlap using PostgreSQL's OVERLAPS operator, replacing complex manual checks with a single query.

Ultra Voucher

Oct 2018 - Nov 2018

Implemented split payment with Java, Spring and MyBatis, letting customers pay for one product across multiple payment accounts. Delivered on time.

Novo Nordisk Indonesia

Jun 2018 - Sep 2018

Designed and developed the web service API for a sales dashboard and analytics platform, adapting the code and database to significant requirement changes mid-project.

Betterway

Jan 2018 - May 2018

Team member on a mobile e-commerce app for a client in the Philippines, built and launched within a 5-month timeline.

PT. Indivara Sejahtera Sukses Makmur

Aug 2017 - Dec 2017

Java Development Trainee

Intensive Java training covering frameworks, basic JavaScript, design patterns and coding principles, followed by on-the-job training as a graduate programmer.

Impactful Works

Rewrote Delivery Routing as a Standalone Engine

Advotics · Delivery Platform

Rewrote the delivery-plan routing out of the legacy delivery service. A planner picks 100–300 orders, and the engine assigns them to vehicles and orders the stops using jsprit, with multiple warehouses, pickup and delivery time windows, vehicle material types and capacity limits. I extended it with custom jsprit constraints for client requirements the library doesn't support, such as scheduled driver breaks and keeping each customer with the same driver. When clients compared plans, the generated routes were more efficient than their manual ones, which often broke time-window or vehicle-material constraints. The solver and distance-matrix building run on AWS Lambda.

Cut Analytics Queries from Over a Minute to Under 500 ms

Advotics · Analytics Platform

Analytics ran on denormalized copies of transaction data in a separate MySQL OLAP database, and queries had slowed to over a minute: billions of rows across all clients, 1M+ per year for one of our largest clients. I rebuilt the ETL pipeline to keep daily and monthly aggregates instead, so an annual revenue query reads 365 daily rows instead of scanning raw transactions. Responses dropped to under 500 ms.

Made Legacy Transactions Atomic, Retryable and Safe from Write Skew

Advotics · Business Transaction & Delivery Platforms

The legacy code had no atomic transactions: an order could be saved without its items. I built @LegacyTransactional, an AOP annotation that opens a session per method, keeps it in a ThreadLocal for a new repository layer, and commits or rolls back on exit. Over time I added read-only transactions routed to the MySQL read replica, retry with exponential backoff on lock and optimistic-lock failures, and per-method isolation with a lock timeout. Optimistic version locking handles lost updates, and SERIALIZABLE handles write skew. One case: a delivery assignment's status depends on its trips, and when two trips of the same assignment completed at the same time, each still saw the other as in progress, so the assignment was left with a stale status. The bug was intermittent and had gone unnoticed for years. Since the fix, assignment statuses have stayed 100% consistent. The annotation became the standard for new endpoints in that service.

Cut the Route-Planning Data API from 60s+ to Under 1s

Advotics · Delivery Platform

The API that loads data for route planning took over 60 seconds because of N+1 queries, slow SQL and sequential blocking calls. I removed the N+1 pattern, rewrote the slow queries and moved the remaining calls to non-blocking Spring WebFlux. Response time dropped to under 1 second.

Traced Nonsensical Sales Travel Distances to Three Root Causes

Advotics · User Identity Platform

Sales tracking for thousands of field sales representatives (reps) was intermittently reporting impossible travel distances between check-in points, a problem that had gone unnoticed for years and made sales reports unreliable. I traced the report to three separate causes. Reps checking in and out from two phones produced out-of-order records, so I enforced a check-out before any new check-in. The midnight scheduler that automatically checks out and logs out reps who forgot generated duplicate primary keys, silently dropping one of the records, so I fixed how those key values are generated. Fake-GPS check-ins were placing reps in Russia, which I traced to the mobile app and handed to the mobile team to fix.

Closed a User-Enumeration and Email-Exposure Hole in Password Reset

Advotics · User Identity Platform

The forgot-password endpoint told attackers which emails were registered ("email not found"), and for registered accounts it returned the full email address, masked only in the frontend. I moved the masking to the server and made the response look the same whether or not the account exists, returning a dummy masked email for unregistered addresses. To stop timing from giving it away, I measured the request with System.nanoTime() and padded the failure path to the typical success-path latency, plus 0–100 ms of random jitter.

Replaced a 6,000-Line Endpoint Router with Annotations

Advotics · Business Transaction Platform

A legacy module mapped every endpoint path to its handler through one custom switch statement that grew past 6,000 lines, until the module stopped compiling. Moving to Spring controllers would have broken backward compatibility, so I built a @HandlerPath annotation placed directly on handler service classes, registered at startup through a Spring event listener, with HTTP-method checks and payload-validation annotations (@EnumPayload, @DatePayload, @JsonPayload). I also built Swagger-like API docs generated from the same annotations. It became the standard for new endpoints in that service.

Replaced AWS SQS with Redis Streams

Advotics · Analytics Platform

I was asked to migrate the queue from AWS SQS to Redis Pub/Sub. I proposed Redis Streams instead, because Pub/Sub is fire-and-forget and doesn't fit a queue. It runs on the Redis instance we already had, at no added infrastructure cost. Consumers use consumer groups and acknowledge only after success. A scheduler retries failures, and those that keep failing go to a dead-letter stream. Merged to production before I left, with SQS scheduled for shutdown.

Cut the Page with Pagination from 5s+ to Around 100 ms

Advotics · Business Transaction Platform

The Page with Pagination in production suddenly took 5 seconds or more to load. It used numeric pagination: a COUNT(*) over every matching order to show the page count, plus a LIMIT/OFFSET query that gets more expensive the deeper the page. I gave the product manager three options with their trade-offs: a date-range filter, a "load more" button that skips the count entirely, and cursor-based paging that filters by the last ID shown instead of using OFFSET. They chose the date-range filter because it needed the least development and no UI/UX change, and we capped the range at 31 days, so each request only counts and pages through one month of orders at most. Load time dropped to around 100 ms.

Cut PDF Feature Effort from 13 to 2–5 Story Points

Advotics · Business Transaction Platform

Each new PDF built with the iText API took about 13 story points, and complex invoices (merged cells, dashed borders, client logos, custom signature blocks) were close to impossible. I built an HTML-to-PDF module (data object → Thymeleaf → HTML → PDF), so layouts are designed in HTML and CSS. New PDF features dropped to 2–5 story points, and the module became the team's standard for PDFs. The same templating module is also reused to build HTML emails.

Redesigned the Bulk-Upload Module to Be Testable

Advotics · Business Transaction Platform

The bulk-upload module (CSV/XLSX) had no tests: static methods, reuse by inheritance and hidden dependencies made it impossible to mock, and every feature added a case to a long switch. I redesigned it around dependency inversion and composition: processor strategies registered in a map and created through factories instead of the switch, adapters so new processors could reuse legacy logic, and a wrapper that validates input with Hibernate Validator and runs validation asynchronously instead of one item at a time. The processors can now be mocked, so the module is finally covered by unit tests.

Set a >90% Unit-Test Coverage Bar the Team Adopted

Advotics

When unit tests became mandatory across all platforms, services had 0–30% coverage. I held my new code above 90% (JUnit, Mockito, BDD style with AssertJ soft assertions). The team followed, and it became the standard.

Skills

☕ Languages

Java (primary), SQL, TypeScript/JavaScript, Groovy.

🧩 Frameworks

Spring Boot, Spring WebFlux, Hibernate, QueryDSL, Spring AOP, Hibernate Validator, Thymeleaf, MyBatis, Grails.

🗄️ Data & Messaging

PostgreSQL, MySQL, Redis Streams, AWS SQS, transaction isolation and locking, OLTP/OLAP ETL.

☁️ Cloud & Optimization

AWS Lambda, jsprit (vehicle routing with time windows and skills).

🧪 Testing & Code Quality

JUnit, Mockito, AssertJ (BDD, soft assertions), >90% coverage on new endpoints, design patterns (Strategy, Adapter, Builder, Factory Method, Command, Façade).

🌍 English

B2 Upper Intermediate (EF SET 55/100, September 2026). Used for documentation and day-to-day collaboration.

Contact Me

Feel free to reach out to me through any of the following channels: