Microservices are an architectural style that structures an application as a collection of services that are:
- Highly maintainable and testable
- Loosely coupled
- Independently deployable
- Organized around business capabilities
- Owned by a small team
Conway’s Law states that “any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure”.
If your organization has multiple teams working on different parts of a larger system, then microservices can serve as a separation of concerns that map an independent, encapsulated service to a specific team. These teams have clear boundaries of responsibility and can deploy on their own schedule.
These factors lend themselves to a polyglot approach. What this means is that because each service is independently developed and deployed, loosely coupled, and owned (potentially) by different teams, these services can be written in different languages.
But what’s the best programming language for writing microservices?
It depends.
What does it depend on? A number of factors.
In this article, I’ll examine six languages I would consider for my next microservices project, and examine them in light of several factors, including technical considerations, social (ecosystem) considerations, and economic considerations.
The six that I’ll cover:
These six languages were chosen based on the following factors:
- Popularity (as determined by this index).
- Suitability for microservices (for example: PHP is popular, but not a popular choice for microservices).
- Best in a particular class (for example: Haskell is not high in absolute popularity, but among functional language advocates, it is high).
I mention a number of other notable languages that didn’t make the cut for the top six, but are still worthy of consideration. I leave it to you to do the comparison for those.
So, let’s dive in. Here are some of the best languages for microservices.
Java
Created in 1995, Java is one of the most popular programming languages in the world. The length of time it has been maintained and its popularity mean it is a solid choice. There is a saying that “nobody ever got fired for buying IBM,” and the same could be said for Java — Nobody ever got fired for choosing Java as the implementation language.
Java can’t be divorced from the Java Virtual Machine (JVM), a cross-platform execution environment that allows Java code to be “written once, run anywhere.” The JVM allows for economies of scale.
You are unlikely to deploy your service on multiple operating systems, but you do benefit from the economies of scale created by developers being able to switch between target operating systems. This means there is a larger market of developers who target the JVM to hire from.
This economy of scale also means there is a massive ecosystem. Spring Boot is a popular and powerful framework that can be used to build microservices in Java. Micronaut is another popular option.
A major problem with early versions of the JVM was performance. The JVM is an abstraction layer, and any abstraction introduces overhead. Modern JVM versions have solved performance at runtime, particularly GraalVM. To deal with the startup overhead, Quarkus is an option. This is particularly an issue for reactive architectures such as Function-as-a-Service (lambda functions).
You can also target the JVM with other languages such as Kotlin, Scala, or Ballerina. Using these languages, you can leverage the existing Java ecosystem of libraries and frameworks through Java-interoperability.
Java does suffer from the “billion dollar mistake” of the Null Pointer Exception. Java 8 introduced the Optional type to guard against this. Kotlin uses non-nullable types, which makes this protection a default feature of the language.
Here is some example Java code using Spring Boot to implement a REST route:
package com.example.springboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
Conclusion
Pros
- Massive ecosystem of support.
- Long history of successful enterprise applications.
- Large pool of developers.
Cons
- Verbose syntax (alt-JVM langs like Kotlin address this).
- Wide variation in the skill levels of developers.
When does it make sense?
- You already have a Java skill set.
- You want a large pool of developers to hire from, or to outsource some development.
- You want to leverage JVM expertise for deployment and management and have the option of alt-JVM languages.
C#
C# is a programming language originally developed by Microsoft as a response to Sun blocking them from extending the JVM and the Java language on Windows – a move that, if Sun had allowed it, would have eroded the “write once, run anywhere” value proposition of Java.
C# is very similar to Java in its syntax. They are both modern C-based languages with an object-oriented focus and functional influences. As noted, C# started its life as Microsoft’s Windows-only alternative to Java. The .NET VM is the underlying virtual machine targeted by C# compiled intermediate language.
In recent years, the .NET VM has gone cross-platform, with .NET Core, allowing C# code to run on Linux and Mac operating systems. This allows developers to target Linux servers with C# code.
Due to its massive installed base on Windows–even though cross-platform is relatively new to .NET and C#–there is a huge ecosystem of libraries. Microsoft’s own framework ASP.NET can be used to develop microservices.
C# 8.0 introduced nullable reference types as a guard against Null Pointer Exceptions at runtime.
Here is some C# code implementing a REST route in ASP.NET:
[ApiController]
public class PeopleController : ControllerBase
{
[HttpGet("people/all")]
public ActionResult> GetAll()
{
return new []
{
new Person { Name = "Ana" },
new Person { Name = "Felipe" },
new Person { Name = "Emillia" }
};
}
}
Conclusion
Pros
- Solid support from Microsoft, with a large installed base and deep pockets.
- Significant pool of developers.
Cons
- Verbose syntax.
- Wide variation in the skill levels of developers.
When does it make sense?
- You already have a C# skill set.
- You’re all about Windows
Go
Go is a modern network and systems programming language, developed by Google and released as 1.0 in 2012. It is a statically typed, compiled language that produces statically linked native binaries, rather than intermediate code for execution in a VM, in contrast to Java and C#.
Go was developed to provide a modern, low-level system and network programming language that balances power (for example, pointers) and safety (for example, memory management).
It includes features like a standardized set of formatting rules and a built-in utility to apply them to the code base. No more bike-shedding about tabs versus spaces.
You can do very low-level network programming with Go, but that’s probably not what you want for microservices. There is a plethora of frameworks for building on top of high-level abstractions, and that can be a challenge. You have the Gin Web Framework, Buffalo, Gorilla, Fiber, and Echo (among others) to choose from.
Many microservice and web frameworks in Go are written by “refugees” from other programming languages. So you’ll find one that is familiar to Ruby on Rails developers, one that is familiar to Node.js developers who have used Express, and so on.
Programmers frequently report “rediscovering the joy of programming” after encountering Go. TJ Holowaychuk, one of the early adopters of Node.js and a major contributor to the Node ecosystem, switched to Go, and wrote an article in 2014 about why he left Node for Go (with an update in 2017).
Go doesn’t have protection from nil pointer exceptions at runtime, although it does have an error, result pattern that enforces error handling, which provides program safety in a large number of cases.
Example Go code:
func ListAction(out http.ResponseWriter, req *router.Request) {
resource, exists := resources[req.Param("resource")]
if exists == false {
//todo: return not found
return
}
action, exists := resource["list"]
if exists == false {
//todo: return not found
return
}
action(out, req)
}
Conclusion
Pros
- A modern language.
- Designed with safety and ergonomics in mind for recent graduate programmers.
Cons
- Wild West ecosystem.
- Smaller pool of developers to hire from.
When does it make sense?
- You are passionate about Go.
- You have a team that will rediscover its passion for programming if allowed to use it.
- You want something safer than JavaScript.
TypeScript
TypeScript is a superset of JavaScript that adds optional strict typing. It transpiles to JavaScript for execution in a JavaScript interpreter.
JavaScript itself is a dynamic and weakly typed language, originally written in 1995 (in ten days) to enable small amounts of interactivity in web pages. It has since scaled up to power entire applications in the browser, and now server-side code with Node.js, Deno, or Bun.
JavaScript managed to get everywhere and become such a widely used language because its “virtual machine” got installed on every single internet-connected computer via inclusion in the web browser, whereas the .NET and Java Virtual Machines did not have such a vector.
As a result of its explosive organic growth, JavaScript as a language has less of an architectured nature than it has an evolved one.
TypeScript was developed to deal with many of the problems that arise when using a dynamic, interpreted language to build large-scale applications. TypeScript makes JavaScript progressively strictly typed. Code bases can be reasoned over by static analysis, refactoring is enhanced, code hinting and autocompletion are enhanced, and transpilation adds a layer of safety.
TypeScript was designed by Anders Hejlsberg, the language designer of C#, so it is familiar to C# and even Java developers.
There is a massive ecosystem of libraries and frameworks for Node.js especially, from the batteries-included, ASP.NET-inspired NestJS, to the minimalist Express.
The NPM (Node Package Manager) website contains thousands of packages you can use in applications.
You can opt-in to Functional Programming patterns using libraries such as fp-ts to guard against runtime Null Pointer Exceptions, but that doesn’t go all the way down the stack. Libraries that you pull in can throw a runtime NPE.
Example TypeScript code using NestJS:
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
Example TypeScript code using Express:
import express, { Express, Request, Response } from 'express';<br><br>const app: Express = express();<br>const port = process.env.PORT;<br><br>app.get('/', (req: Request, res: Response) => {<br> res.send('Express + TypeScript Server');<br>});<br><br>app.listen(port, () => {<br> console.log(<code>[server]: Server is running at https://localhost:${port}
);
});
Conclusion
Pros
- Massive amount of libraries and frameworks.
- Large number of developers who know JavaScript.
- TypeScript introduces safety features that aid less experienced developers.
- One language across the entire stack.
Cons
- Security is a concern with NPM package pollution.
- Massive node_modules directory means applications can be very large.
When does it make sense?
- You want one language across the stack.
- You have front-end engineers you want to work on the backend.
Python
Python is a high-level general-purpose interpreted language. Arguably, Python’s most distinct feature is “meaningful white space.” In place of the curly braces used by C-derived languages, Python uses indentation to indicate nesting.
Originally developed in the early 1980’s, Python is one of the most popular programming languages and is increasing in popularity given its heavy usage in data science.
The Python standard library alone can be used to write gRPC microservices, and there are a number of popular frameworks such as Flask, Django, Nameko, FastAPI, and MinOS that can be leveraged.
Python doesn’t have protection against Null Pointer Exceptions.
Example Python code implementing a REST route using Flask:
from flask import Flask #importing the module
app=Flask(<strong>name</strong>) #instantiating flask object
@app.route('/') #defining a route in the application
def func(): #writing a function to be executed
return 'PythonGeeks'
if <strong>name</strong>=='<strong>main</strong>': #calling main
app.debug=True #setting the debugging option for the application
instance
app.run() #launching the flask's integrated development webserver
Conclusion
Pros
- Long-standing and large community, providing a wide range of libraries.
- Aesthetically pleasing code formatting, if you hate curly braces.
Cons
- Interpreted, so speed can be an issue.
- Multi-threading is tricky.
When does it make sense?
- You already use Python.
- Your domain involves Machine Learning, Data Science, or AI.
Haskell
Haskell is a purely functional language. It is lazily evaluated and statically typed. Explicitly handling effects makes it a good match for concurrent processing. However, it has a very steep learning curve–not simply syntax, but paradigms. Originally developed as an academic language in a university, Haskell has become a popular language for some of the most advanced programmers who dwell deep within the abstractions of a program.
Haskell programs, when they can be compiled, are guaranteed to run without unhandled runtime exceptions every time, 95% of the time. Haskell is extremely stable at runtime when a program is correctly specified.
Mu-haskell is a set of microservices libraries for Haskell. The paradigm of Haskell does not lend itself to frameworks, but rather to libraries.
Example Haskell code implementing a gRPC server:
main :: IO ()
main = runGRpcApp msgProtoBuf 8080 server
server :: (MonadServer m) => SingleServerT Service m _
server = singleService ()
Conclusion
Pros
- Your programs will be correct, and incredibly elegant expressions of logic.
- You will be equally feared and respected by other programmers.
Cons
- Hiring from a smaller pool of developers (offset by less competition).
- Runtime safety and program correctness is emphasized over developer productivity.
When does it make sense?
- You are bored with the programming languages of mortals, and want to write your next program on “Insane mode”.
- You are passionate about functional programming.
Other top languages for microservices: Notable mentions
There are several other languages that didn’t make the cut for this round-up—mostly due to being less popular—but deserve a shout-out:
- Swift – a modern (2014) programming language from Apple. Open-source and cross-platform, it has a number of frameworks such as Vapor and Perfect for writing microservices. Swift is high in popularity, due to its use in iOS apps, but is less widely used for microservices.
- Rust – a modern (2006) programming language originally developed by Mozilla. Rocket and Tide are two frameworks for Rust microservices.
- Dart – a modern (2011) cross-platform programming language from Google. Aqueduct is an all-included framework for Dart microservices.
- Elixir – a modern (2012) functional language that runs on the BEAM VM. It is popular among Ruby on Rails developers looking for a more performant language. Phoenix is a popular Elixir framework.
Composing Microservices
Once your business or functional domain is decomposed into microservices, you have another issue: where does the composition of these services exist as a concern?
In a choreography approach, the composition of the services is split across all the services. In this case, they know about each other, breaking the loose coupling principle, and at the same time making information about the coupling distributed.
In an orchestration approach, the composition of the services is the concern of a single component of the system such as Camunda, a business process engine for microservices orchestration. This component encapsulates the composition concern and provides a single operational interface. By using BPMN as the expression of the composition, you automatically get up-to-date documentation of the entire process composition, as well as a run-time map for operational reporting.
Camunda supports polyglot microservice composition over gRPC, with a number of official and community-supported programming language clients, and the ability to easily create new ones in the programming language of your choice.
While you are looking at the components for your next microservices project, take a look at Camunda for the orchestration piece. It is available as a zero-install SaaS with a free tier to develop on.
Further Resources
- Take a deeper dive into orchestration versus choreography for microservices by watching this talk by Camunda co-founder and chief technologist, Bernd Ruecker: Balancing Choreography and Orchestration • Bernd Rücker • GOTO 2020.
- Learn more about orchestrating microservices with Camunda. Check out Getting started with microservice orchestration | Camunda.
Try Camunda at no cost: get a free, 30 day trial account here.
Editor’s Note: This post was originally published in September 2022, and has been updated for accuracy and completeness.
Start the discussion at forum.camunda.io