Frankenstein in programming is code assembled from incompatible parts of different technologies, styles and architectures. According to ThoughtWorks Technology Radar research (2024), 28% of large projects show signs of Frankenstein syndrome — architectural eclecticism arising from the lack of a unified technical vision. By analogy with Mary Shelley's novel, such code works, but its maintenance turns into a nightmare.
Key Takeaways
Frankenstein (Frankenstein code, Frankenstein pattern) is an antipattern where a software system is assembled from parts not designed to work together. Like Frankenstein's monster, such code can function, but it is ugly, unpredictable, and dangerous at the slightest changes.
The term comes from literature: in Mary Shelley's novel “Frankenstein, or The Modern Prometheus” (1818), a scientist created a living being from fragments of different dead people's bodies. In programming, the analogy is exact — developers take pieces of different frameworks, libraries, languages and glue them together “live”, producing a working but monstrous result.
The difference between Frankenstein and spaghetti code is in scale and nature. Spaghetti code is a tangled structure within one technology stack. Frankenstein is eclecticism at the architecture level: different technologies, incompatible paradigms, conflicting approaches within the same system.
Microservice architecture allows using different technologies for different services, but with clear boundaries and standardized interaction protocols. Frankenstein is chaotic mixing without boundaries: REST and GraphQL in one controller, two ORMs in one module, SQL and NoSQL for one entity.
Lack of a technical leader or architect is the root cause. When there is no person responsible for architectural integrity, each developer chooses tools “for themselves”. One likes Spring, another likes Guice, a third uses a custom DI. The result is an architectural mishmash.
Project merger is the second common cause. Two teams developed their modules independently, using different stacks. When the modules need to be combined into one application, they are simply “glued” together with adapters and middleware. The result is Frankenstein.
Corporate acquisitions are the third scenario. Company A bought Company B and wants to integrate its product into its own. Instead of rewriting — glueing through APIs, shared databases, and band-aids. After a year, the system becomes a monster that no one understands.
| Causes | Description | Typical Result |
|---|---|---|
| No architect | Each developer chooses their own stack | 3 different HTTP clients in one module |
| Project merger | Two products glued into one | Two ORMs, two logging methods |
| M&A | Acquisition of a company with its product | Hybrid of different architectures and styles |
| Experiments | Introducing new technologies without a strategy | Java 8 + Java 21 features in one file |
| Political decisions | Technology imposed from above without context | Enterprise framework for a simple script |
Experienced developers who want to try new technologies in production often become the source of Frankenstein. Instead of limiting experiments to an isolated module, they introduce experimental code into critical parts of the system.
A classic example is using multiple ORMs in one application. Some modules use Hibernate, some use MyBatis, and some use raw JDBC queries. Transactions become unmanageable, cache becomes inconsistent, and a new developer doesn't know which approach to use for a new feature.
A second example is mixing architectural styles. In a REST API controller, you find SOAP service calls, raw SQL queries, file system access, and HTML generation. Such an application is impossible to test, extend, or document.
A third example is a tech stack where Python is used for the backend, Node.js for a microservice, C# for a desktop client, and Java for an Android app, while all business logic is smeared across them without clear separation of responsibilities.
// frankenstein — mixed styles and technologies
// callbacks, Promises, and async/await combined
// callbacks
db.query("SELECT * FROM users", function(err, rows) {
if (err) handleError(err);
// Promise inside callback
fetch("/api/data").then(function(data) {
// async/await inside then
(async () => {
const result = await processData(data);
sendResponse(result);
})();
});
});
// clean code — unified async/await style
async function getUserData(userId) {
const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
const data = await fetch("/api/data/" + userId);
return await processData(user, data);
}
A single database is used simultaneously as an SQL relational (with normalization) and as a NoSQL document-oriented (with JSON columns) one. Some queries go through ORM, some through stored procedures, some through raw SQL from code. The DB schema is undocumented, migrations conflict.
Onboarding complexity is the first consequence. A new developer must know 5 languages, 3 frameworks, 2 architectural styles to understand how the system works. Onboarding stretches from weeks to months. According to LinkedIn (2023), projects with technological eclecticism lose new employees 2 times more often.
Behavior unpredictability is the second consequence. A change in the Python microservice can unexpectedly break the Java module because they share a database without clear contracts. Debugging such problems requires simultaneous knowledge of all technologies in the stack.
Security is the third consequence. Each technology in the stack requires its own security configuration, its own patches, its own monitoring. Maintaining security at an acceptable level for 5-6 heterogeneous technologies is practically impossible. One of them will inevitably be vulnerable.
SonarQube can measure technical debt, but it cannot measure “architectural debt” — component incompatibility. This debt manifests not in linter warnings, but in the inability to add a new feature without modifying three different modules written in different technologies.
The first and main step is to appoint an architect or tech lead responsible for the integrity of the technology stack. This person has veto power over introducing new technologies without an architectural review. Not democracy, but responsible sole decision-making on key technologies.
The second step is to implement the Architecture Decision Record (ADR) process. Any significant architectural decision (DB choice, framework, protocol) is documented as a short text: context, alternatives considered, decision made, consequences. ADRs are stored in the repository and available to the entire team.
The third step is to establish the principle of “one task — one tool”. For HTTP requests — one client. For ORM — one library. For logging — one framework. Exceptions are allowed only through ADR with justification. If the project already has Axios — don't add fetch, if it has SLF4J — don't write via System.out.
Experiments are allowed, but in an isolated environment. Allocate a module or service that can be rewritten with a new technology without affecting the rest of the system. If the experiment succeeds — standardize it via ADR. If not — remove it without consequences.
Inventory — the first step. Create a complete map of the technology stack: what frameworks, libraries, languages, protocols are used, in which modules and for what tasks. You will see the scale of the problem: tool duplication, conflicting technologies, unused dependencies.
Standardization — the second step. Choose one tool for each task. For example: only Hibernate for ORM, only SLF4J + Logback for logging, only REST for API. Document the standard in ADR. Start replacing from modules where eclecticism causes the most problems.
Parallel Run strategy — the third step. The old and new tools work in parallel until the new one proves its reliability. For example, the old HTTP client and the new one work simultaneously, but the new one only handles part of the requests. After a stabilization period, the old one is removed.
// frankenstein — three HTTP approaches in one project
// Module A: OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);
// Module B: RestTemplate (Spring)
restTemplate.getForObject(url, String.class);
// Module C: java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
// unified approach: RestTemplate for sync, WebClient for reactive
@Autowired
private RestTemplate restTemplate;
public String callApi(String url) {
return restTemplate.getForObject(url, String.class);
}
A technical leader is the main tool for fighting Frankenstein. Not a manager, not an architect in an ivory tower, but a practicing developer who writes code, reviews PRs, and makes architectural decisions. Without such a person, the project inevitably slides into technological eclecticism.
RFC (Request for Comments) is a process borrowed from Open Source communities. Before introducing any significant technology, the author writes an RFC: problem, proposed solution, alternatives, implementation plan. The team discusses, votes, accepts or rejects. RFC creates transparency and prevents “quiet” architectural decisions.
The Technology Radar (ThoughtWorks Technology Radar) is a categorization tool: Adopt, Trial, Assess, Hold. The team regularly reviews the radar and updates statuses. This helps distinguish “trendy” from “useful” and avoid introducing unproven technologies into critical code.
The most important quality of architecture is consistency. Even the best tool used throughout the entire project is not better than the best tool used only in one module... Actually, even a not-so-great tool used throughout the entire project is better than the best tool used only in one module. Consistency reduces cognitive load, simplifies onboarding, and makes code predictable.
Frequently Asked Questions
Polyglot persistence is the conscious use of different databases for different tasks (PostgreSQL for transactions, Redis for caching, Elasticsearch for search). Frankenstein is chaotic mixing without strategy. The difference lies in having an architectural decision: polyglot is a plan, Frankenstein is its absence.
Yes, and this is a common problem. When each microservice uses its own language, its own database, its own protocol and its own deployment approach without centralized standards — you get a distributed Frankenstein. For microservices, common standards are important: a unified protocol (REST/gRPC), a common log format, centralized observability.
Don't forbid — guide. Suggest the author write an RFC: describe why the existing solution doesn't fit, what alternatives were considered, how migration will be done. Often in the process of writing an RFC, the developer themselves realizes that the new technology is not needed. If the RFC is convincing — implement it, but with a plan and constraints.
First, inventory, then standardization. Don't try to rewrite everything at once. Select one layer (e.g., HTTP clients or logging), pick a single tool, write an ADR, and migrate gradually. The Strangler Fig pattern — replace old components with new ones one by one without stopping the application.
The fewer, the better. Ideally — one language, one framework, one database, one logging method. Realistically — 2-3 languages (with clear separation), 1-2 databases, 1-2 frameworks. Each additional technology increases the team's cognitive load and maintenance cost.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also