
Blog Post

When Software Migrations Endanger a Company: A Practical Case Study
Software migrations are among the most challenging projects in IT and require a clear strategy, comprehensive documentation, and long-term planning that equally considers daily operations and future expansions. But what happens when a migration descends into chaos due to poor decisions, unstructured processes, and lack of support? In this article, I share an experience that shows how chaotic conditions and wrong priorities can jeopardize a project and put the company in a critical situation.
The Starting Point: Migration to React Native
The company decided to migrate its existing app from native Android and iOS platforms to a centralized React Native platform. The optimal approach for such a migration would be a feature freeze for the old apps (no new features but continued bug fixing) while simultaneously building the new React Native application. This way, the company could have maintained a clear separation between old and new development.
The Wrong Decision: Hybrid Parallel Development
Instead of following this proven strategy, they decided to set up React Native in parallel within the existing native Android and iOS projects. Normally, a React Native application is set up via the React Native CLI or Expo. The CLI offers the possibility for deep native customizations and creates Android and iOS folders to hold the native code parts.
In this project, however, they decided to remove the Android and iOS folders generated by the CLI. Instead, separate repositories mirrored the native Android and iOS parts, resulting in three repositories: one for the React Native project and one each for the native Android and iOS components.
This division caused significant complexity, as the React Native development was theoretically centralized but practically still dependent on the separate repositories for Android and iOS. The plan was to switch from React Native CLI to Expo later, once the native components were fully migrated. However, since this was not the case, they had to continue working with the CLI, even though the later switch to Expo would require additional adjustments due to the different initialization methods for certain SDKs. This double intertwining between CLI and Expo led to a complex system that was extremely problematic from the outset.
I also mentioned that Expo was the wrong choice for a project of this size and market relevance, as an Expo app is significantly larger compared to a standard CLI app. The app size influences the placement in the stores, as algorithms from Google and Apple include the size in their evaluation. This well-intentioned advice, however, was met with incomprehension, and I was told that I was probably referring to the Expo Go app. Moreover, it was claimed that the Expo workflow does not lead to a larger app than a standard CLI application. This statement is factually incorrect. With a monthly user base of several million active customers, this could lead to revenue losses of millions per month, as placement in the store rankings is crucial.
The Reality in the Team: Decentralized and Specialized Developer Structures
The challenges in this project were exacerbated by the team structure: over 30 developers worked on the application, but each only had specific, specialized tasks. No one in the team possessed the overall understanding necessary for an effective migration. There was no developer who was thoroughly familiar with Android, iOS, React Native (CLI and Expo), and the SDK used. Instead, the knowledge was fragmented, and over the years, no one was able to effectively advance the migration, and the team was almost entirely replaced during the migration period without prior documentation being created.
The company was very fortunate to find someone with my experience, who brought the necessary knowledge in Android, iOS, React Native (CLI and Expo), as well as knowledge about the SDK used. But instead of utilizing this expertise and trusting my structured approach, they met me with distrust and massive demands for quick results. The technical project manager was primarily focused on delivering quick results to stakeholders rather than explaining the actual complexity of the project and prioritizing a realistic solution. Instead of systematically addressing the problem, he applied pressure without even remotely understanding the technical challenges. Such an attitude, coupled with distrust towards an expert, ultimately led to a catastrophic situation.
Technical Complications and Creative Stopgap Solutions
Without the native Android and iOS folders in the React Native repository, the application could not be started independently. To get the application running anyway, when starting the application in the React Native repository, a complete emulator with the latest version for Android (or iOS) and a CI/CD automated bundle was first loaded. Only through this process was it possible to access it via the Metro bundler and incorporate the latest code from the React Native repository into the application. This meant that developers were always forced to load a full native emulator and use the Metro bundler for each change to update the code. This highly inefficient and cumbersome process greatly hampered development and led to a highly fragmented and time-consuming workflow.
This complicated structure laid the foundation for the deep structural problems of the project.
Five Years of Migration Without Consistency
Over the course of five years, features were gradually transferred to React Native, but without gradually reducing the native components. The native code spanned more than 20 modules, some of which were connected to React Native via bridges and had to be maintained in parallel. Within the React Native environment, further fragmentation arose due to the mixture of class-based components and functional components.
Certain parts of the app were implemented with outdated class-based components that are long obsolete. Initially, they started with JavaScript, and later TypeScript was introduced (JavaScript remained), but without strict type checks, which alone led to over 500 files with errors in React Native. Code quality rules were not consistently enforced, and there was a lack of a consistent linter configuration, so outdated and inconsistent patterns remained permanently. Functional components and hooks were partially integrated, which posed a challenge since hooks cannot be used in class-based components. Instead of completely replacing these old components, additional code was written to simulate the functionality of the hooks. This mixture led to a confusing and hard-to-maintain codebase in the React Native project.
Another challenge was the additional integration of web functionalities within individual React Native modules, which led to events being triggered for both the app and the web simultaneously in the development environment. This occurred due to a faulty implementation that did not consider the separation of platforms, leading to unforeseen interactions between the app and web components.
The Challenge of Third-Party Libraries, Bridges, and Native SDK Migrations
The app used several third-party libraries that were essential for the operation and analysis of the application. The libraries were partly integrated into the native Android and iOS projects and partly into React Native, which meant that the libraries had to be initialized and maintained in parallel. This created a close version binding between the native SDKs and the React Native libraries.
Particularly problematic was the integration of push notifications via Firebase, which was originally implemented in the native Android and iOS projects and represented a central functionality of the SDK to be migrated. The goal was to gradually transfer the functions of the SDK to React Native, which, however, required both native and React Native-specific adjustments. Since the SDK was already partially implemented natively and partially in React Native, certain components of the push notifications had to continue to be maintained natively. This hybrid structure led to a strong interweaving and made it virtually impossible to fully and consistently migrate the push functionalities to React Native within the required time.
Bridges further complicated the situation. Some SDK functions had already been migrated to React Native, while others remained native and were accessed via the bridges. This constellation required that non-migrated SDK parts could access the native components through the bridges, maximizing the dependencies between React Native and the native components. An SDK update thus became enormously complicated and required extensive adjustments in React Native as well as in the native Android and iOS projects—a virtually impossible and extremely time-consuming process.
My Approach as a Consultant and the Lack of Trust in the Team
As a consultant, I was tasked with migrating the SDK from native implementation to React Native without knowing how fragmented and intertwined the system was. To untangle this complexity and create a viable solution, I developed a clear plan:
- Analysis of the native code components and their functionality: The goal was to understand the SDK functionalities in the native code and identify existing problems and dependencies. The native components and their connections to React Native should be analyzed in detail.
- Creation of an event list: Since no documentation existed, it was necessary to create a complete list of all events, their attributes, and their usage locations to get a comprehensive overview of the event structure.
- Inventory in React Native: The next step was to take stock of the SDK functionalities already implemented in React Native. It should be checked which dependencies still referred to the native components through the existing bridges and which of them could be integrated into React Native.
- Implementation and adjustments: Based on the insights gained, the required migration and adjustment steps should be planned and implemented to fully integrate the native SDK components into React Native.
- Testing and documentation: Finally, comprehensive tests should be conducted, and complete documentation should be created in Confluence to record all changes and migration processes and to facilitate future teams' entry.
Distrust, Pressure to Justify, and Questioning My Approach
After only eight days, I was pressured by the technical project manager to provide a precise estimated time of arrival (ETA) for completion. Although I clearly stated that a well-founded estimate was only possible after the analysis (I had just completed point 2), this fell on deaf ears. Additionally, I was asked to present the unfinished documentation—a clear sign of distrust towards my work. Despite my transparent approach, I had to repeatedly justify myself and provide preview documents, even though I intended to post the complete documentation only after completing the analysis.
Furthermore, inexperienced developers criticized my structured approach, arguing that the analysis phase and documentation were unnecessary. My systematic plan was labeled as "exaggerated" and "unnecessary," although the complex dependencies and lack of documentation precisely required this structured approach. The management not only lacked trust in the plan but also repeatedly questioned my experience and was driven by the "can-do" attitude of the technical project manager, who ignored the problems and increased the pressure on me to present supposed progress to the stakeholders. Instead of explaining the actual situation, revealing the technical challenges, and prioritizing realistic solutions, he valued showing immediate results without the necessary technical understanding. Such an attitude, coupled with distrust towards an expert, only made the project more difficult and ultimately led to a catastrophe.
The Point of Exit
By the tenth day, I was again under massive pressure when the project management realized that certain API levels under Android were no longer supported with the old SDK version (a point that should have been known since August 2022). This led to users no longer being reachable, which was an urgent problem.
Consequently, I was instructed to perform an immediate migration—and this within an extremely tight timeframe of only three days. I was supposed to remove or adjust native code components and "unimportant" functionalities of the SDK if necessary to achieve a short-term solution. Such an approach, however, would have ignored the intertwining of the SDK and the many dependencies. The instruction to make adjustments as quickly as possible also meant that I would have to remove parts of the code without sufficient knowledge of the impacts. This carried the risk that the application would break at unforeseen points or important functions would be lost.
The project management ignored in their demand for quick results the complex structure of the app and the necessary effort to ensure a sustainable migration. The unrealistic demands and the expectation to deliver short-term results without considering the technical consequences and circumstances ultimately led me to decide to leave the project after just over two weeks.
Another aspect to mention is that cultural differences in working methods might have contributed to the rejection of my structured approach. All people involved in the project lived abroad or had only recently been in Germany, which may have led to a different understanding regarding project planning and implementation.
Lessons Learned: Avoiding Migration Disasters
This experience shows how essential a clear and well-thought-out migration strategy is and how destructive distrust, lack of understanding, and unrealistic expectations can be for such a complex project.
Important insights:
- Clear strategy and separation of codebase: A feature freeze and the parallel, independent development of the new application would have organized and stabilized the project.
- Consistent architecture and avoidance of technological mixing: Outdated code patterns and inconsistent use of components should be avoided.
- Trust and constructive criticism: Consultants and developers need backing and the freedom to develop structured solutions.
- Realistic timelines: Timeframes should be based on well-founded analyses and not on exaggerated expectations.
- Professional collaboration: Criticism should be constructive and based on professional grounds.
Conclusion
Software migrations are always challenging and risky. This project has shown that a clear strategy, trust, and open communication are the cornerstones for a successful migration. Without these fundamental principles, a migration quickly becomes a disaster—with serious consequences for the company and the people involved.

