Spring Boot Python Executor: Integrating Java and Python



This content originally appeared on DEV Community and was authored by w4t3r

Introduction

If you have an idea or experience with Java + Python integration, I am going to show you how to make a simple connection between these programming languages in the Spring Boot ecosystem.

Also, for additional information about spring-boot-python-executor you can check out the repository of the project: https://github.com/w4t3rcs/spring-boot-python-executor

Project Setup

Add the starter dependency in pom.xml:

<dependency>
    <groupId>io.github.w4t3rcs</groupId>
    <artifactId>spring-boot-python-executor-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Or to your build.gradle:

implementation 'io.github.w4t3rcs:spring-boot-python-executor-starter:1.0.0'

Also, I’d like to suggest using Docker as a sandbox for Python script execution, so add to your docker-compose.yaml this:

services:
  python-grpc-server:
    image: "w4t3rcs/spring-boot-python-executor-python-grpc-server:latest"
    ports:
      - "50051:50051"
    environment:
      PYTHON_SERVER_TOKEN: secret
      PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas,scipy

And your basic application.yaml should look like this:

spring:
  python:
    executor:
      type: grpc
      grpc:
        token: secret

Developing the first Services with Python

In this chapter, I’ll show you how to communicate with Python code by coding out first simple classes that use Python.
Our first example is going to be just a simple class that executes Python code without any difficult logic:

SimplePythonService

@Slf4j
@Service
@RequiredArgsConstructor
public class SimplePythonService {
    private static final String SIMPLE_SCRIPT = "simple_script.py";
    private static final String NUMERIC_SCRIPT = "numeric_script.py";
    private static final String DICT_SCRIPT = "dict_script.py";
    private final PythonProcessor pythonProcessor;

    @PythonBefores({
            @PythonBefore(SIMPLE_SCRIPT),
            @PythonBefore(NUMERIC_SCRIPT),
            @PythonBefore(DICT_SCRIPT),
    })
    public void doSomethingWithPythonBefore() {
        log.info("doSomethingWithPythonBefore()");
    }

    public void doSomethingWithPythonInside() {
        log.info("doSomethingWithPythonInside()");
        log.info("1 --> {}", pythonProcessor.process(SIMPLE_SCRIPT, String.class));
        log.info("2 --> {}", pythonProcessor.process(NUMERIC_SCRIPT, Float.class));
        log.info("3 --> {}", pythonProcessor.process(DICT_SCRIPT, DictScriptResponse.class));
    }

    @PythonAfters({
            @PythonAfter(SIMPLE_SCRIPT),
            @PythonAfter(NUMERIC_SCRIPT),
            @PythonAfter(DICT_SCRIPT),
    })
    public void doSomethingWithPythonAfter() {
        log.info("doSomethingWithPythonAfter()");
    }
}

simple_script.py

hello_world = 'Hello World'
print(hello_world)
o4java{hello_world}

numeric_script.py

a = 2.3462
b = 14.151
c = a + b
o4java{c}

dict_script.py

result = {
    "x": "Hello World",
    "y": 12345
}

o4java{result}

In this example, we can see different types of executing Python scripts using PythonBefore or PythonAfter annotations, or PythonProcessor object.

  • Use PythonBefore if you want to execute a Python script before a Java method and forget about it.
  • PythonProcessor is used for advanced purposes: getting the result of the Python script execution as a Java object and argument configuration. It is a centralized object for executing Python scripts in the library.
  • Use PythonAfter if you want to execute a Python script after a Java method.
  • o4java{…} means that this Python field will be returned as a Java object.

More advanced example with Spelython usage

PriceCalculatorPythonService

@Service
@RequiredArgsConstructor
public class PriceCalculatorPythonService {
    private static final String CALCULATOR_SCRIPT = "price_calculator.py";
    private final PythonProcessor pythonProcessor;

    public double calculatePrice(ProductDto product, CustomerDto customer) {
        int randomMultiplier = new Random().nextInt(10);
        product.setBasePrice(product.getBasePrice() * randomMultiplier);
        Map<String, Object> arguments = Map.of(
                "product", product,
                "customer", customer
        );

        PythonExecutionResponse<Double> response = pythonProcessor.process(CALCULATOR_SCRIPT, Double.class, arguments);
        return response.body();
    }
}

price_calculator.py

base_price = spel{#product.getBasePrice()}
discount = 0

# Apply customer loyalty discount
if spel{#customer.loyaltyYears()} > 2:
    discount += 0.05

# Apply volume discount
if spel{#product.getQuantity()} > 10:
    discount += 0.03

final_price = base_price * (1 - discount)

# Return the calculated price to Java
o4java{final_price}

This example shows how to use Spelython (SpEL + Python) in your Python script. Write SpEL expressions inside spel{…}, and the special SpelythonResolver object will resolve it as a JSON object in your Python script.

Advanced machine learning example

MLPythonService

@Slf4j
@Service
@RequiredArgsConstructor
public class MLPythonService {
    private static final String ML_SCRIPT = "ml_script.py";
    private final PythonProcessor pythonProcessor;

    public double processMLScript(String text) {
        Map<String, Object> arguments = Map.of("text", text);
        PythonExecutionResponse<Double> response = pythonProcessor.process(ML_SCRIPT, Double.class, arguments);
        return response.body();
    }
}

ml_script.py

# Required imports
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

# Simple sentiment analysis using predefined positive and negative words
positive_words = ['good', 'great', 'excellent', 'amazing', 'wonderful', 'best', 'love']
negative_words = ['bad', 'terrible', 'awful', 'worst', 'hate', 'poor', 'disappointing']

# Get the input text from Java
text = spel{#text}.lower()

# Count positive and negative words
positive_count = sum(1 for word in positive_words if word in text)
negative_count = sum(1 for word in negative_words if word in text)

# Calculate sentiment score (-1 to 1)
total = positive_count + negative_count
if total == 0:
    sentiment = 0
else:
    sentiment = (positive_count - negative_count) / total

# Return the sentiment score to Java
o4java{sentiment}

Conclusion

spring-boot-python-executor enables safe and modular Python integration into Spring Boot applications. With AOP, REST, and gRPC support, the library is production-ready and easily extensible for custom use cases.


This content originally appeared on DEV Community and was authored by w4t3r