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.