Getting Started with Thrift: Building a Java Server and Python Client

Posted on in programming

cover image for article

In our first article, we introduced Thrift and Protobuf and laid out our plan to build cross-language client/server applications. Now, it’s time to dive into a hands-on example with Thrift. In this article, we’ll define a simple service using Thrift’s IDL, implement a server in Java, and write a Python client that can call the server’s methods. This example sets the foundation for more complex scenarios we’ll tackle later in the series.

What We’re Building

To keep things simple, we’ll create a Calculator service that supports basic arithmetic operations. Our service will have a couple of RPC methods, like add and subtract, which return the result of the given operations.

High-Level Steps:

  1. Write the Thrift IDL file that defines the service interface.
  2. Generate code for Java and Python using the Thrift compiler.
  3. Implement the server in Java.
  4. Implement the client in Python.
  5. Run the server and test the client calls.

Prerequisites

Before following along, ensure you have:

  • Thrift Compiler: Installed and in your system’s PATH.
    Refer to the Thrift Installation Guide for platform-specific instructions.
  • Java Development Kit (JDK): A recent JDK (e.g., OpenJDK 11 or later).
  • Maven or Gradle (Optional): For building Java projects. This example will assume Maven.
  • Python 3 and pip: For running the Python client.
  • Thrift Python Library: Can be installed via pip install thrift once you have the generated code.

Step 1: Define the Thrift IDL

Create a file called calculator.thrift (in a directory of your choosing) with the following content:

namespace java com.example.calculator
namespace py calculator_py

service Calculator {
  i32 add(1:i32 num1, 2:i32 num2)
  i32 subtract(1:i32 num1, 2:i32 num2)
}

What’s happening here?

  • namespace directives: Assign language-specific namespaces. For Java, we’ll use com.example.calculator; for Python, calculator_py.
  • service Calculator: Defines a service named Calculator.
  • Methods add and subtract: Each takes two integers and returns an integer.

Step 2: Generate Code

Use the Thrift compiler to generate Java and Python code from the calculator.thrift IDL.

Generate Java Code:

thrift --gen java calculator.thrift

This creates a gen-java directory with Java classes for the Calculator service.

Generate Python Code:

thrift --gen py calculator.thrift

This creates a gen-py directory containing Python modules for the service.

After generation, you’ll have:

  • gen-java/com/example/calculator containing classes like Calculator.java, Calculator$Client.java, etc.
  • gen-py/calculator_py containing Python modules like Calculator.py and supporting files.

Step 3: Implement the Java Server

We’ll create a simple Java Maven project to host the server. Inside your project directory, you might have a structure like:

.
├── pom.xml
├── calculator.thrift
└── src
    └── main
        └── java
            └── com
                └── example
                    └── server
                        └── CalculatorHandler.java

CalculatorHandler.java will implement the Calculator.Iface interface generated by Thrift.

CalculatorHandler.java:

package com.example.server;

import com.example.calculator.Calculator;
import org.apache.thrift.TException;

public class CalculatorHandler implements Calculator.Iface {
    @Override
    public int add(int num1, int num2) throws TException {
        System.out.println("Received add request: " + num1 + " + " + num2);
        return num1 + num2;
    }

    @Override
    public int subtract(int num1, int num2) throws TException {
        System.out.println("Received subtract request: " + num1 + " - " + num2);
        return num1 - num2;
    }
}

Server.java:

package com.example.server;

import com.example.calculator.Calculator;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

public class Server {
    public static void main(String[] args) {
        try {
            CalculatorHandler handler = new CalculatorHandler();
            Calculator.Processor<CalculatorHandler> processor = new Calculator.Processor<>(handler);

            TServerTransport serverTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(
                new TServer.Args(serverTransport).processor(processor)
            );

            System.out.println("Starting the Thrift server on port 9090...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

What’s happening here?

  • We create a CalculatorHandler that implements the server-side logic.
  • We create a TSimpleServer listening on port 9090.
  • When the server runs, it listens for requests and uses our handler to respond.

Make sure to include Thrift libraries in your pom.xml. The Maven dependency might look like:

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.15.0</version>
</dependency>

Compile and Run the Server:

mvn clean package
java -cp target/my-server.jar com.example.server.Server

You should see:

Starting the Thrift server on port 9090...

Step 4: Implement the Python Client

In Python, we’ll use the generated Python stubs and the Thrift runtime library.

Install the Thrift Python library if you haven’t already:

pip install thrift

client.py:

import sys
sys.path.append('gen-py')  # Ensure Python can find the generated code

from calculator_py import Calculator
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

def main():
    # Connect to the server
    transport = TSocket.TSocket('localhost', 9090)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    client = Calculator.Client(protocol)

    transport.open()

    # Test add method
    result = client.add(10, 5)
    print("add(10, 5) = ", result)

    # Test subtract method
    result = client.subtract(10, 5)
    print("subtract(10, 5) = ", result)

    transport.close()

if __name__ == '__main__':
    main()

What’s happening here?

  • We create a Thrift client to connect to localhost:9090.
  • We call the add and subtract methods on the server.
  • Results are printed to the console.

Run the Client:

With the server running, in another terminal:

python client.py

You should see:

add(10, 5) = 15
subtract(10, 5) = 5

The server console will show requests being processed.

Verifying Cross-Language Communication

We just demonstrated a fully functional RPC setup:

  • A Java server implemented using Thrift-generated code.
  • A Python client calling the server’s methods.

Both sides generated their code from the same calculator.thrift file, ensuring consistency and type safety. This is exactly the kind of cross-language interoperability that Thrift enables.

Next Steps

You’ve successfully built a minimal Thrift-based system spanning two languages. This foundation will help you understand how to extend these principles to more complex services and multiple languages.

In the next article, we’ll expand our Thrift usage to include Rust, showing how we can integrate a third language into this ecosystem. Whether we add a Rust client calling the existing Java server or convert the server logic into Rust, we’ll deepen our understanding of interoperability and performance considerations.


Stay tuned for the next article where we introduce Rust into the Thrift environment, further demonstrating how flexible these frameworks can be across different programming languages.

Part 2 of the Cross-Language Client/Server Applications with Thrift and Protobuf series

Slaptijack's Koding Kraken