Java Spring Boot backend: where a mobile product breaks
The growth in the number of requests from devices is almost never linear: after enabling notifications, offline mode, and background sync, one screen starts pulling several requests, and the background pulls just as many.
The load on a mobile app backend grows in steps, and the first thing to hit the limit is the database connection pool: the queue accumulates, response time starts to degrade — from hundreds of milliseconds to seconds at peak.
The client does not wait for a response, the user sees an empty screen, although the data is on the server.
The second bottleneck in data exchange is synchronization between device and database.
While the connection is stable, divergence is unnoticeable; in the subway, elevator, or roaming, a request breaks midway, a retry arrives on top of an already applied operation, and duplicates appear in the database: two identical orders, a doubled counter, a repeated write.
Data sync failures accumulate silently — divergence is discovered when support starts reconciling tables manually.
The third area is background tasks: exports, rating recalculations, mailings. Launched in the same thread pool as user requests, they degrade response time for the entire application at the worst possible moment.
We break down how this is closed: where to draw the boundary between online traffic and background processing, how to make operations idempotent, and what read caching gives.
What changes in service operation after core rework
Peak loads reveal what is not visible on staging: the request queue grows faster than the core can process them, and some calls time out.
After reworking the core on Java and Spring Boot, hot operations moved to separate handlers, background tasks got their own limiters, and cache stopped competing with transactions for the same connections. The service withstands peak instead of surviving it on stretched responses.
Recovery time no longer depends on the on-call engineer. Liveness and readiness checks, automatic restart of failed instances, and predictable warm-up bring the service up in minutes instead of tens of minutes of manual actions.
Peak hour stopped being the worst time for an incident: downtime shrank so much that such cases no longer make it into reporting.
A separate result is the eliminated class of failures. Connection leaks, requests in a loop, and shared resource locks previously surfaced only under load, that is, at the most inconvenient moment.
Now these scenarios are cut off at the architecture level and run through load tests before release, so the same incidents do not return.
Release of updates accelerated for the same reason. Contracts between app and server are backward compatible, schema migrations go as a separate step, the new version first receives part of traffic — rollback takes one command. The release cycle shrank from weeks to days.
Formats of backend organization: comparison of approaches
The backend format determines how quickly the product will start hitting its own limits.
A monolith on Spring Boot is one codebase, one deployment process, and one data schema: transactions are enclosed within the application, debugging is end-to-end, starting development is cheapest in time.
This is how you should start while one team works on the product and domain boundaries are still moving.
Such a scheme hits a limit not on load, but on coordination: a change in a shared module affects everyone, release goes out as a whole, and there is one test environment for all.
Then the choice is not between "good" and "bad," but between where to draw the boundary — inside one process or between services with their own database and their own release cycle.
| Option | When it fits | Limitations |
|---|---|---|
| Monolithic backend | One team, domain boundaries still moving | Scales only as a whole, shared release, full test run for any change |
| Modular backend | Domain is already readable, several teams | Requires discipline around module boundaries; shared process and database are a single point of failure |
| Microservice approach | Parts of the system have different load profiles and rates of change | Network failures, distributed transactions, separate observability and release cycle per service |
| Hybrid scheme | Core is stable, individual functions are loaded or change often | Two operating models at once; rules are needed for what and when is moved out |
The practical criterion is one: draw the boundary where a rare but expensive change crosses it. If extracting a service does not reduce release time or isolate failure, it only adds points that can fail.
How work on the backend is structured: stages
The backend of a mobile app on Spring Boot rarely breaks at the start — failures come during growth: data schemas diverge between environments, the connection pool hits its ceiling, and behavior under load is discovered from user complaints.
Therefore work proceeds in sequential stages, and each is closed with a verifiable result: without it we do not start the next step, so that rework does not move into production.
Stages and control points
- Backend audit. We examine code and configurations: service boundaries, database work, connection pool, error handling. Output — a list of bottlenecks with risk assessment and order of elimination.
- Target architecture scheme. We fix module composition, exchange contracts with the app, data models, and migration order. An agreed scheme becomes the basis for timeline estimates.
- Environment setup. We separate profiles for development, checks, and production, move secrets out of the repository, and enable automated builds. The test environment starts behaving like production.
- Implementation in portions. Changes go in small sets with automated tests for business logic; the control point — covered critical paths and no regression in existing scenarios.
- Load testing. We run scenarios close to the real traffic profile: response time, connection pool behavior, degradation at peaks. Based on results, we adjust settings and build in headroom.
- Handover to maintenance. We provide the architecture scheme, configurations, release and rollback regulations, metrics, and alerting rules. Incidents are investigated from documentation, not from the author's memory.
Configurations, schemas, and regulations: handover package
Handing over a backend is not an archive with source code, but a package by which another team brings up environments, understands contracts, and continues maintenance without us.
A Java Spring Boot project is handed over together with a description of how it is built and launched — without verbal explanations and "ask the previous developer."
- Environment configurations — profiles for development, pre-production, and production: database connections, queues, external services, variable sets. Secrets are kept separately and do not get into the repository.
- Data exchange schemas — interface specification, request and response formats, error codes, versioning rules. Mobile client and server change according to this schema, not via chat.
- Access policies — roles and permissions, session lifetime, procedure for updating and revoking access keys, restrictions on service calls.
- Backend documentation — module map, database schema with table purposes, migration order, description of background tasks and schedules.
- Maintenance regulations — how a release is deployed, how it is rolled back, what is checked during a failure, who receives alerts, how dependencies are updated.
- Instructions for deployment from scratch — sequence of steps and verification after each, so the environment comes up reproducibly, not from one engineer's memory.
- List of external dependencies — what is connected and under what terms, where keys are stored, how service availability is checked.
Each artifact goes through acceptance: the second team brings up the environment according to the instructions, performs a test release and rollback. If something has to be explained verbally, then there is a gap in the package, and we close it before handover.
Case: reduced share of failures during data synchronization
The product — a mobile app for field employees: requests, photo reports, and warehouse stock accumulate locally and go to the server when connectivity appears.
The backend runs on Java Spring Boot, and as the number of agents grew, synchronization became a bottleneck: in the morning and evening devices went online almost simultaneously.
What was before the changes
The client sent the local package as a whole and retried on any error. At peak, requests piled into an avalanche: the share of failed synchronizations reached 7%, some operations were written twice, and response time stretched to several seconds. Support received about a hundred tickets per week.
What was changed
Synchronization was made step-by-step and idempotent: each operation gets a key on the device, so resending does not create a second record.
The client fetches only changes after its marker, reference data is served from cache, and heavy aggregates are moved to background processing.
Retries go with increasing delay and jitter — devices do not hit the server simultaneously; write parallelism is limited, and the queue at peak is unloaded by priorities.
What became
The share of failed synchronizations is less than 0.5%, duplicates in reports are gone as a class. Response time at peaks shrank from several seconds to fractions of a second, and support tickets about synchronization became about four times fewer. The freed-up time the team spends on new features, not on investigating data discrepancies.
How does a modular backend differ from a microservice one?
In a modular backend, the application remains one process and one database, and boundaries between subsystems run along modules and build rules: orders, payments, notifications live side by side, but address each other only through declared interfaces.
This separation of responsibilities is maintained by static analysis and mandatory code review, not by the network between services. The microservice approach makes the same boundaries physical: a separate process, its own data schema, its own contract with versioning.
The difference shows up in operations, not in the diagram on the board. A modular application needs one build pipeline, one entry point, and one set of migrations — failure is localized by logs inside the process.
Microservices need distributed tracing, retries, and compensations instead of transactions: failure becomes non-obvious and turns into a separate diagnostic task.
The speed of change is determined by the number of teams. While the product is led by one or two teams, an end-to-end scenario changes with one set of edits and one release, and splitting into services does not add this speed, but takes it away.
When five teams with different release cycles work on the product, independent deployments start to pay off — but only with well-established contracts and observability.
Maintenance complexity is the key selection criterion. Microservices shift it from code to infrastructure: orchestration, autoscaling, unified logs, degradation schemes, backward compatibility of contracts. A modular backend keeps this cost noticeably lower while load and team grow predictably.
Technical doubts: limitations, migration, compatibility
Engineering doubts at the start concern not functionality, but the transition period: what will happen to already installed applications, how to move load without stopping releases, and how much maintenance inherited code will require.
We migrate through parallel operation of the old and new service on Spring Boot: routing by client version, backward-compatible schema migrations, comparison of responses on production traffic.
Switching becomes a change in traffic share, not a nightly release with downtime, and rollback is done with one setting.
Can the backend be migrated without stopping releases?
Yes. Both environments run simultaneously, traffic is shifted in shares, divergences are caught by comparing responses on live requests. No release stoppage is needed: the next app release goes out on the usual schedule.
What will happen to old versions of the app?
Users update over months, so the API remains backward compatible: new fields are added, old ones are not removed until the share of old versions falls below the threshold. Disabling a version is a separate decision based on telemetry, not a side effect of server update.
How do you address accumulated technical debt?
By criticality: first what breaks releases or prevents load growth, then readability and test coverage. Each step is closed with checks in the build, so the debt does not return the same way.
How heavy will maintenance be?
Scheduled dependency updates, incident investigation by metrics, degradation control. The load drops when alerts are configured on symptoms, not on everything.
Let's discuss the task: what to send for assessment and work plan
For a substantive timeline estimate, we need not a full documentation package, but a minimal set by which scope and risks are visible.
The more specific the input data, the fewer assumptions in the implementation plan and the more accurate the work estimate — without "we'll clarify as we go."
What to send:
- Scenarios and roles: what tasks the user solves, what is available to each role, where offline work is critical.
- Screen structure: mockups or at least a list of screens and transitions between them.
- Integrations: list of external systems, exchange formats, who owns each side, and whether ready interfaces exist.
- Access and environments: repository, test environment, accounts for checking scenarios, data exchange documentation.
- Current state: what is already implemented, where it is deployed, what we reuse. If the backend is on Java Spring Boot, we will separately note which modules are suitable as is and which will require rework.
- Constraints: platforms, personal data protection requirements, expected load, and update release procedure.
If something from the list is missing — it does not block the start: we will close part with questions on a call, and part we will record as assumptions directly in the plan.
Describe the task and send what you already have — we will come back with a timeline estimate, implementation stages, and team composition for your case.







