Apache Thrift: Cross-Language RPC Made Simple

Learn Apache Thrift — Facebook-born RPC framework for building cross-language services. Define once in Thrift IDL, generate clients in Python, Java, Go, C++, and 20+ languages.

Apache Thrift: Cross-Language RPC Made Simple illustration
On this page8 sections

Before gRPC existed, Facebook needed a way for their services — written in Python, C++, Java, PHP, and Erlang — to talk to each other efficiently. They built Apache Thrift, an RPC framework that generates client and server code in 28+ languages from a single interface definition. Thrift has been battle-tested at Facebook scale (billions of RPC calls per second) and remains a strong choice for heterogeneous microservice architectures.

What is Apache Thrift?

Thrift is a cross-language RPC framework with three key components:

  • Interface Definition Language (IDL): A .thrift file that defines your data types and services — like a .proto file for gRPC.
  • Code Generator: Generates client/server stubs in your target language(s) from the IDL.
  • Runtime Library: Handles serialization (multiple protocols), transport (sockets, HTTP, memory), and server models (threaded, non-blocking, forked).
Thrift Architecture — Pluggable Layers
Your Code (Service Handlers)Business logic — implement the generated service interface
Generated Code (Processor)Auto-generated from .thrift file — routes calls to your handlers
Protocol (Serialization)Binary, Compact, JSON, or custom — how data is encoded on the wire
Transport (I/O)Socket, HTTP, framed, buffered, in-memory — how bytes are moved
Server (Concurrency Model)Simple, threaded, non-blocking, forked — how requests are handled

Step 1: Define Your Service (.thrift)

// user_service.thrift
namespace py user_service
namespace java com.example.users
namespace go users

// Enums
enum Department {
  UNKNOWN = 0,
  ENGINEERING = 1,
  MARKETING = 2,
  SALES = 3,
}

// Custom exception
exception UserNotFoundException {
  1: string message,
  2: string user_id,
}

// Data structures
struct User {
  1: required string id,
  2: required string name,
  3: required string email,
  4: optional i32 age,
  5: Department department = Department.UNKNOWN,
  6: list<string> roles,
  7: map<string, string> metadata,
}

struct CreateUserRequest {
  1: required string name,
  2: required string email,
  3: optional i32 age,
  4: Department department,
}

struct ListUsersResponse {
  1: list<User> users,
  2: i32 total_count,
}

// Service definition
service UserService {
  User getUser(1: string id) throws (1: UserNotFoundException e),
  User createUser(1: CreateUserRequest request),
  ListUsersResponse listUsers(1: i32 limit, 2: i32 offset),
  void deleteUser(1: string id) throws (1: UserNotFoundException e),
  bool ping(),
}

Step 2: Generate Code

# Install Thrift compiler
# macOS:
brew install thrift

# Ubuntu:
sudo apt install thrift-compiler

# Generate Python code
thrift --gen py user_service.thrift
# Creates: gen-py/user_service/UserService.py, ttypes.py, constants.py

# Generate Java code
thrift --gen java user_service.thrift
# Creates: gen-java/com/example/users/UserService.java, User.java, etc.

# Generate Go code
thrift --gen go user_service.thrift

# Generate C++ code
thrift --gen cpp user_service.thrift

# Generate multiple languages at once
thrift --gen py --gen java --gen go user_service.thrift

Step 3: Implement the Server (Python)

# pip install thrift
import uuid
from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

# Import generated code
from gen_py.user_service import UserService
from gen_py.user_service.ttypes import (
    User, CreateUserRequest, ListUsersResponse,
    UserNotFoundException, Department,
)

# In-memory database
users_db = {}

class UserServiceHandler:
    def ping(self):
        return True

    def getUser(self, id):
        if id not in users_db:
            raise UserNotFoundException(
                message=f"User {id} not found",
                user_id=id,
            )
        return users_db[id]

    def createUser(self, request):
        user_id = str(uuid.uuid4())
        user = User(
            id=user_id,
            name=request.name,
            email=request.email,
            age=request.age,
            department=request.department or Department.UNKNOWN,
            roles=[],
            metadata={},
        )
        users_db[user_id] = user
        return user

    def listUsers(self, limit, offset):
        all_users = list(users_db.values())
        page = all_users[offset:offset + limit]
        return ListUsersResponse(
            users=page,
            total_count=len(all_users),
        )

    def deleteUser(self, id):
        if id not in users_db:
            raise UserNotFoundException(
                message=f"User {id} not found",
                user_id=id,
            )
        del users_db[id]

# Create and start the server
handler = UserServiceHandler()
processor = UserService.Processor(handler)
transport = TSocket.TServerSocket(host="127.0.0.1", port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()

server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
print("Thrift server running on port 9090")
server.serve()

Step 4: Use the Client

from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from gen_py.user_service import UserService
from gen_py.user_service.ttypes import CreateUserRequest, Department

# Connect to the server
transport = TSocket.TSocket("localhost", 9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = UserService.Client(protocol)

transport.open()

# Ping
print(f"Server alive: {client.ping()}")  # True

# Create a user
user = client.createUser(CreateUserRequest(
    name="Alice",
    email="alice@example.com",
    age=30,
    department=Department.ENGINEERING,
))
print(f"Created: {user.id} - {user.name}")

# Get user
fetched = client.getUser(user.id)
print(f"Fetched: {fetched.name}, {fetched.email}")

# List users
response = client.listUsers(limit=10, offset=0)
print(f"Total users: {response.total_count}")
for u in response.users:
    print(f"  - {u.name} ({u.email})")

# Handle errors
try:
    client.getUser("nonexistent-id")
except Exception as e:
    print(f"Error: {e}")  # UserNotFoundException

transport.close()

Thrift vs gRPC — Which Should You Choose?

Thrift vs gRPC Comparison
Feature Apache Thrift gRPC
OriginFacebook (2007)Google (2015)
Language support28+ languages12+ languages
TransportPluggable (TCP, HTTP, memory)HTTP/2 only
SerializationPluggable (Binary, Compact, JSON)Protobuf only
StreamingNot built-in4 types (unary, server, client, bidi)
EcosystemMature but smallerLarge, growing rapidly (CNCF)
Server modelsSimple, threaded, non-blocking, forkedAsync (language-dependent)
Best forPolyglot envs, custom transportsCloud-native, Kubernetes, streaming

When to Choose Thrift

  • 28+ language support: If your stack includes niche languages (Erlang, Haskell, OCaml, Perl, D, Lua), Thrift has better coverage than gRPC.
  • Pluggable transports: Need to run over raw TCP sockets, shared memory, or custom transports? Thrift's transport layer is swappable.
  • Multiple serialization formats: Choose Binary (fastest), Compact (smallest), or JSON (debuggable) per-service.
  • Existing Thrift infrastructure: Many companies (Facebook/Meta, Evernote, Cassandra) already use Thrift — stick with what works.

When to Choose gRPC Instead

  • Streaming is needed: gRPC's bidirectional streaming is built-in. Thrift requires workarounds.
  • Cloud-native/Kubernetes: gRPC has first-class support in Envoy, Istio, and most service meshes.
  • Larger ecosystem: More tutorials, more tools, more community support in 2026.
  • Starting fresh: If you're building a new system, gRPC is the safer bet for long-term ecosystem support.

Apache Thrift remains a powerful, production-proven RPC framework. Its pluggable architecture and unmatched language support make it ideal for heterogeneous environments. If you're already in the Thrift ecosystem or need extreme flexibility in transport and serialization, Thrift is an excellent choice. For greenfield projects, evaluate both Thrift and gRPC against your specific needs — you can't go wrong with either.

Share this article

Stuck on implementation?

Get private, 1-on-1 help with system design, performance, scaling, or any technical challenge.

Book a Session

Related Production Resources

Course

Free learning tracks

Turn this guide into a structured production engineering path.

Lab

Interactive engineering labs

Practice the same ideas through scenario-based simulators.

Reference

Production cheatsheets

Keep the operational commands and checks nearby.

Glossary

Key terms

Review the vocabulary behind the architecture.

Discussion

Questions, corrections, or production notes? Add them here so other learners can benefit.

Continue Reading

Related practical guides from the same production engineering path.

Backend 22 min read

Distributed Systems Algorithms: Consensus, Replication, and Coordination at Production Scale

How real distributed systems agree, replicate, and coordinate. Raft and Paxos consensus, leader election in etcd and Kafka, quorum reads in Cassandra, gossip in Redis Cluster, vector clocks, CRDTs, and the consistency models that determine what your system can promise.

Distributed Systems Consensus
Backend 18 min read

Rate Limiting Algorithms: Token Bucket, Sliding Window, and Distributed Rate Limiters in Production

How API gateways, edge proxies, and service meshes throttle traffic without breaking legitimate users. Token bucket vs leaky bucket, fixed and sliding windows, distributed rate limiting with Redis, Envoy and NGINX implementations, and adaptive rate limiting under attack.

Rate Limiting API Gateway