AI works in any language. But not equally well. This choice has become secondary for the solo developer and the main one for a team over the years ahead. A breakdown across five criteria, a comparison of languages, and practical takeaways for tech leads and architects.
This article continues the "methodology + AI" block: why you need a methodology with AI, how to review AI code, how to set up the tooling. Here we look at which substrate this stack works best on.
Framing the question
In individual work with Claude/Copilot/Cursor, the difference between languages is smoothed out. AI knows the syntax of any popular language well enough to write working code. A personal project in Rust, a prototype in Python, a Bash script — it makes no difference.
In a team of 10+ engineers with a product built to last years, the difference is huge. Because AI amplifies the properties of a language:
- If the language is consistent and its rules are strict, AI writes evenly. The team reads each other's code the same way.
- If the language leaves room for interpretation, AI does it "its own way" every time. The team gets 10 different styles for one task.
This is not theory. This is an observation from a backend team of 20+ engineers: the same AI behaves radically differently in Java and in Python.
Five criteria of an AI-friendly language
1. Volume in the training set
Major languages (Python, JavaScript, Java) are represented in the training data of models by orders of magnitude more than rare ones (Haskell, Erlang, Ada). The more data, the more accurately AI generates, and the fewer hallucinations.
This is the most obvious criterion. But it alone does not determine everything — otherwise Python would be the undisputed leader, whereas in practice it is often worse than Java for a team (more on this below).
2. Type strictness
Strongly typed languages (Java, Kotlin, TypeScript, Rust, C#) catch AI hallucinations at compile time. AI invented a method findFirstByXyz — the compiler did not find it, error right away. Cheap to fix.
Weakly typed ones (Python, JavaScript, Ruby) discover such hallucinations at runtime. The test passed the main scenario, and a week later in production — AttributeError: 'NoneType' object has no attribute 'foo'. Expensive.
The paradox: Python, with its 1st place by training-data volume, loses to Java in third place precisely because of this difference. AI writes more hallucinations in Python, and they live longer.
3. Strength of conventions
The fewer ways there are to do it "right", the fewer discrepancies between developers and AI sessions. Go has one way to format code (gofmt), one way to resolve dependencies (go modules), one way to handle errors (if err != nil). In Scala or Ruby, every other project invents its own embedded mini-languages.
Java sits in the middle here: Spring Boot sets strong conventions, but without it and without an explicit code rulebook you get services of every shape and size. With Use Case Pattern and AI skills this problem is solved — a methodology on top of the language.
TypeScript with a framework (Angular, NestJS) behaves like Java with Spring — conventions make AI more even. Without a framework, plain Node.js produces more noise.
4. Code volume and syntax density
You often hear: "for AI the main thing is less code, fewer tokens, less context to carry". This is half a myth. Let's split it — what is actually true, and what is a misconception.
What is true
- Boilerplate written by hand is a real burden. AI writes the getter
getUserId() { return userId; }for the thousandth time, spends tokens, sometimes misses. Code generation (Lombok in Java,data classin Kotlin,dataclassin Python, struct embedding in Go) removes this entirely. - A long file with contradictions means a higher chance of inconsistency. AI remembers the start of the file, forgets the middle, and contradicts itself at the end. Here shorter = better.
- Less hidden behavior — explicit semantics lower the chance of hallucinations.
What is a myth (and why)
1. AI does not suffer from volume the way a human does. Context windows are 200K–1M tokens. 100 vs 1000 lines — for AI it is almost the same thing. A programmer tired after 1000 lines makes mistakes. AI does not.
2. AI accuracy depends on EXPLICITNESS, not on BREVITY. Explicit annotations and types (for example, @Transactional, @PreAuthorize in Spring; decorators in NestJS; Protocol in Python) make the code longer, but each decision is visible locally. AI read 20 lines of a handler and understood everything. Haskell looks elegant in 5 lines, but half the behavior is inferred from types in other files — AI follows chains and gets confused more often.
3. Brevity through metaprogramming = disaster. Ruby's User.find_by_email_and_active_and_role(email, true, "admin") is shorter than Java, but it is method_missing — the method does not actually exist, it is created on the fly from the name. AI generates similar calls; sometimes they work, sometimes they do not. In Java the same call would be repository.findByEmailAndActiveAndRole(...) — longer, but AI sees whether the method exists in the interface. Harder to get wrong.
4. A short source vs a compiled one are different things. Compile-time code generation (Lombok @Data in Java, data class in Kotlin) lets you write a short source with explicit semantics — without a hand-written getter/setter/equals/hashCode. AI sees the source (short) AND the behavior types (explicit through the annotation). This is the best of both worlds.
5. The metric is not "lines", but "how much context you must hold per operation". A 30-line handler — AI reads 30 lines, done. Scala with implicit conversions in 10 lines — AI has to know which implicit type conversions apply at this point, what the priorities are, how they combine. That is a search across several files and the project context.
The sweet spot
Minimum source code + maximum explicit semantics. How to achieve it:
- Java + Lombok + records + jOOQ-generated — the source is 30–40% of the volume of "bare" Java, while the semantics stay explicit (types and annotations are visible).
- Kotlin + Spring —
data class+ nullable types do the same natively. A bit shorter than Java+Lombok, a bit less transparent. - Go — the standard library and conventions are deliberately boring. One way to write everything. AI writes very predictably, because there are few options.
- TypeScript + NestJS — decorators do what Spring annotations do. Medium volume, explicit semantics.
- Python + Pydantic + mypy —
BaseModeland strict type hints bring the behavior closer to strongly typed languages.
What does not work for reducing code without losing AI-friendly properties:
- Embedded mini-languages and metaprogramming. Scala with implicit type conversions, Ruby with
method_missing, Python with metaclasses — AI loses the thread, because half the behavior is hidden. - Magic with hot reload (Spring without annotations, pure YAML config). The source of truth is blurred between code and config.
Short formula: AI loves explicit code + minimum boilerplate repetition. Not "short syntax", not "magic that is shorter". Compile-time code generation — excellent. Dynamic magic at runtime — bad.
Java vs Go in the age of AI: what actually sets them apart
Before AI, writing speed was a real selection criterion. The picture was like this:
- Python — the fastest: dynamic typing, minimal ceremony, a prototype in half a day.
- Spring Boot Java — catches up thanks to annotations and code generation (Lombok removes repetitive code, jOOQ generates database access, MapStruct handles mappers between layers). On a complex domain model it is often faster than Python, because the structure is set by the framework rather than reinvented each time.
- Go — in between: fast for network services, but verbose error handling (
if err != nilin every call) and the long absence of generics (until 1.18) slowed down a complex domain. - "Bare" Java without frameworks — genuinely slow, hence the entrenched stereotype "Java is verbose".
Now that AI does the writing, the difference in speed is erased. Python, Spring Boot Java, Go — all take roughly the same time to write. Typing speed is no longer the main selection criterion.
What remains:
| Criterion | Java (Spring + Lombok) | Go |
|---|---|---|
| Volume in the training set | ✅ One of the leaders | ✅ Large volume, especially cloud services |
| Domain model complexity | ✅ Records, sealed types, generics, tooling for DDD | 🟡 Simple type system, generics are new, DDD aggregates come out clunky |
| Conventions and uniformity | 🟡 Many frameworks, but Spring Boot dominates | ✅ One way for everything (gofmt, error handling, modules) |
| Depth of code analysis | ✅ IntelliJ level, ArchUnit, Spotbugs | 🟡 vet, golangci-lint — good, but less deep |
| Startup / memory / cold start | 🟡 The JVM warms up for seconds | ✅ The binary starts instantly |
| Ecosystem for the domain | ✅ Spring, Hibernate, jOOQ, MapStruct, Resilience4j | 🟡 Fewer options for complex business logic |
| Readiness for regulated industries | ✅ Dominates in banks, government companies | 🟡 In some industries considered "new", requires justification |
What to choose when:
Take Java if:
- Complex domain model with invariants and domain events (DDD territory)
- A long-lived product built for years with a team of 10+ people
- A regulated industry (banks, government companies, healthcare)
- You need deep integration with the corporate stack (SSO, audit, monitoring)
- You are at Use Case Pattern level 3 (DDD + Hexagonal)
Take Go if:
- Network services, gateways, proxies, infrastructure utilities
- High throughput with simple business logic (CRUD, ETL)
- A microservice with a fast cold start (serverless functions, an autoscaling container)
- A small team (2–4 people) that values low cognitive load
- Concurrent code that naturally maps onto goroutine + channel
Hybrid model (which I often see in reality):
- Java/Kotlin for domain services (Order, Catalog, Payment) — where the logic is complex
- Go for the periphery — gateway, proxying, lightweight entry points, infrastructure services
- The team knows both, but the methodological rules (Use Case Pattern, DDD) apply first and foremost to the complex domain services. Go services inherit the shared conventions but do not require the full tooling set.
In this picture the question "Java or Go" is not "either/or" but "what goes where". It used to be that the language choice was dictated by one programmer's development speed. Now — by the characteristics of the task and the maturity of the ecosystem for it.
5. Quality and strictness of the tooling
Linters, formatters, static analysis, type checking, dependency analysis. The stricter the tools, the faster the feedback from AI code reaches the team.
Java is unrivaled here: Spotbugs, Checkstyle, ArchUnit, IntelliJ-level analysis. AI writes — five tools check it before the tests even run.
Python is catching up (mypy, ruff, bandit), but the ecosystem is fragmented: each team picks its own set, and AI does not know it in advance.
JavaScript / TypeScript — ESLint + Prettier + tsc — the standard.
Comparison table: a language's AI-friendliness for a team
Scale: ✅ good for AI in a team, 🟡 tolerable, ⚠️ needs compensating measures.
| Language | Volume in the training data | Typing | Conventions | Tooling | Bottom line for a team |
|---|---|---|---|---|---|
| Java (Spring Boot) | ✅ | ✅ | ✅ | ✅ | ✅ |
| TypeScript (NestJS/Next) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Kotlin (Spring/Ktor) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Go | ✅ | ✅ | ✅ | ✅ | ✅ |
| C# (.NET) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Python (with types + ruff + mypy) | ✅ | 🟡 | ⚠️ | 🟡 | 🟡 |
| JavaScript (without TS) | ✅ | ⚠️ | ⚠️ | 🟡 | ⚠️ |
| Rust | 🟡 | ✅ | ✅ | ✅ | 🟡 |
| Swift | 🟡 | ✅ | ✅ | ✅ | 🟡 |
| C++ | ✅ | 🟡 | ⚠️ | 🟡 | ⚠️ |
| Scala | 🟡 | ✅ | ⚠️ | 🟡 | ⚠️ |
| Haskell | ⚠️ | ✅ | 🟡 | 🟡 | ⚠️ |
| Ruby (Rails) | 🟡 | ⚠️ | ✅ | 🟡 | ⚠️ |
| PHP | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
"⚠️ Needs compensating measures" = the language can be used, but the team has to invest explicitly in extra tooling and rules, otherwise AI will create chaos faster than code review can catch it.
What this means in practice
If you can choose your stack
Pick from the top tier: Java, TypeScript, Kotlin, Go, C#. These are not "trendy" languages, they are languages whose AI-friendly characteristics do not need to be bolted on by hand.
A special observation about Java: contrary to its reputation as "verbose and outdated", it turned out to be a strong choice precisely for the age of AI. Spring Boot sets conventions, Lombok removes repetitive code, jOOQ generates database access, ArchUnit checks the architecture, and IntelliJ-level analysis closes the feedback loop — AI works maximally stably on this stack. Look at the large fintech and online-retail teams running AI experiments: most are on Java/Kotlin, not on "trendy" Rust or Python.
If you already can't
What compensates for a weak language:
Python in a team:
- Mandatory type hints across the whole codebase + mypy in CI as a blocker
- ruff with settings stricter than the defaults
- pytest tests on 100% of the business logic (not "80% coverage", but "every business function is tested")
- Pydantic for all DTOs/models — as a replacement for strong typing
- pre-commit hooks with local mypy + ruff
JavaScript in a team:
- Move to TypeScript. If you can't — JSDoc with types + tsc in check mode, to type at least something.
- Strict ESLint (
airbnb-typescriptorstandard-with-typescript) - Husky + lint-staged for pre-commit
C++ in a team:
- A modern dialect (C++17+), a ban on raw new/delete
- clang-tidy in strict mode, sanitizers on every CI run
- AddressSanitizer, ThreadSanitizer — mandatory in tests
- Header-only where possible, so AI does not get lost in include chains
The general idea: a strong language gives AI a friendly environment out of the box, a weak language requires manual retrofitting. The team pays engineering time to compensate.
If you have a multi-language team
The most expensive case. The AI style in each language is its own, uniformity across languages is impossible, and the reviewer has to remember N different standards.
Solutions:
- One primary language for business logic, the rest for specialized tasks (Python for ML, Go for network I/O). A clear boundary.
- One set of architectural principles (Use Case Pattern or an equivalent) on top of all languages. The specific code differs, the philosophy is one.
- AI skills per language separately. This is an investment, but it pays off on large teams.
What does not matter (contrary to popular belief)
- The age of the language. Java is 20 years older than Rust. In AI-friendliness Java is ahead — because there is more training data, stronger conventions, more powerful tools.
- Performance. AI writes equally correct code in "fast" Rust and in "slow" Python. Runtime speed is a separate question.
- "Modernity". Scala and Haskell are more modern than Java in their type system. But AI is weaker on them, because there is less training data and the conventions are fragmented.
- The team's personal preferences. This is an emotional factor, not an engineering one. Preferences matter for retention and hiring, but have nothing to do with AI-friendliness.
Connection to Use Case Pattern
Use Case Pattern supports several languages: Java/Spring, Go, Node.js/TypeScript, and Python are covered by the persistence, code-style, and test-strategy standards. The methodology works in each of them with language-specific tooling.
Java/Spring remains the most mature track: the full tooling set (Lombok, jOOQ, Spring Boot, MapStruct, ArchUnit) gives AI stable idiomatic templates, while audit requirements in regulated industries (banks, government companies) make Java the de facto standard for such teams.
For Kotlin, Go, TypeScript/Node, and Python — the architectural principles (UseCase + Handler + Repository, spec-as-code, AI skills for review) apply fully. The specific tools and skills are language-specific.
If the stack is already chosen — see the "If you already can't" section above and the Use Case Pattern standards for your language.
What to do with this in a team
For the tech lead of a new service:
- Choose from the top tier. If the organization uses a single stack on principle, then any top-tier language will work with the right conventions and tooling.
- Do not choose Python, Ruby, or JavaScript for a production service with serious regulatory requirements just because "the team knows it". The cost of compensating for the language's weaknesses is at least 20-30% of engineering time on specialized tooling.
- Do not choose Rust, Scala, or Haskell just because they are "trendy". AI works noticeably slower on them because of the smaller training-data volume.
For the architect of a corporate portfolio:
- If there is a long migration plan — move toward one of the top tier. Even if you do not rewrite specific services, build the new ones on it.
- Reconsider a multi-language strategy. Before the age of AI it made sense ("the best language for each task"). With AI its cost is higher: the AI tooling has to be maintained N times.
For the CTO of a startup:
- Do not let "we choose for hiring speed" override "we choose for durability with AI". Hiring in 2 years will be different — candidates already mostly work with AI, and they come from top-tier languages.
- Java/TypeScript at the start will give you, 18 months in, a team whose code stayed clear and maintainable. Do not repeat "a prototype in Python because it's fast" — followed by a six-month rewrite.
What's next
- If you haven't read it yet — the core block on methodology: why with AI, how to review, how to set up the tooling
- Starting a new service and want to set the stack right from day one — adopting Use Case Pattern
- Server application architecture from start to production — choosing the initial architecture