Cloud Native 21 min read

Building a Spring Boot gRPC Microservice Demo Deployed on Istio with Kubernetes

This article walks through creating a simple Spring Boot microservice using gRPC, packaging it with Maven, containerizing it with Docker, and deploying both the server and client to a Kubernetes cluster with Istio sidecar injection, demonstrating how Istio removes service‑mesh logic from business code and enables seamless multi‑language microservice communication.

Architect
Architect
Architect
Building a Spring Boot gRPC Microservice Demo Deployed on Istio with Kubernetes

As a fan of Service Mesh and cloud‑native technologies, the author builds a minimal Spring Boot microservice demo that uses gRPC for communication and deploys it on Istio. The demo sends a string and returns a string, illustrating how Istio isolates service‑governance logic into a sidecar.

Initially the author considered Spring MVC but switched to Spring Boot for simplicity.

Why use Istio? Istio moves governance logic out of the business code into an independent sidecar process, eliminating code coupling, supporting multiple languages, and integrating smoothly with Kubernetes.

Why choose gRPC? gRPC offers lightweight, high‑performance RPC with Protobuf and HTTP/2, integrates well with Istio, and avoids the complexity of Spring Cloud’s SDKs.

Project Setup

Use Spring Initializr to create a parent project spring-boot-istio and add gRPC dependencies. The pom.xml for the parent module includes modules for API, server, and client, and manages the grpc-all dependency.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <modules>
        <module>spring-boot-istio-api</module>
        <module>spring-boot-istio-server</module>
        <module>spring-boot-istio-client</module>
    </modules>
    ...
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-all</artifactId>
                <version>1.28.1</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

The API module defines the protobuf service:

syntax = "proto3";
option java_package = "site.wendev.spring.boot.istio.api";
option java_outer_classname = "HelloWorldService";
package helloworld;
service HelloWorld {
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; }

Server Implementation

Implement the service logic:

/**
 * Service implementation
 */
@Slf4j
@Component
public class HelloServiceImpl extends HelloWorldGrpc.HelloWorldImplBase {
    @Override
    public void sayHello(HelloWorldService.HelloRequest request,
                         StreamObserver<HelloWorldService.HelloResponse> responseObserver) {
        HelloWorldService.HelloResponse response = HelloWorldService.HelloResponse
                .newBuilder()
                .setMessage(String.format("Hello, %s. This message comes from gRPC.", request.getName()))
                .build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
        log.info("Client Message Received:[{}]", request.getName());
    }
}

Configure and start the gRPC server as a Spring bean:

@Slf4j
@Component
public class GrpcServerConfiguration {
    @Autowired HelloServiceImpl service;
    @Value("${grpc.server-port}") private int port;
    private Server server;
    public void start() throws IOException {
        log.info("Starting gRPC on port {}.", port);
        server = ServerBuilder.forPort(port).addService(service).build().start();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.info("Shutting down gRPC server.");
            stop();
        }));
    }
    private void stop() { if (server != null) server.shutdown(); }
    public void block() throws InterruptedException { if (server != null) server.awaitTermination(); }
}

Hook the server lifecycle into Spring Boot using CommandLineRunner :

@Component
public class GrpcCommandLineRunner implements CommandLineRunner {
    @Autowired GrpcServerConfiguration configuration;
    @Override public void run(String... args) throws Exception {
        configuration.start();
        configuration.block();
    }
}

Client Implementation

Expose a REST endpoint that forwards requests to the gRPC server:

@RestController
@Slf4j
public class HelloController {
    @Autowired GrpcClientConfiguration configuration;
    @GetMapping("/hello")
    public String hello(@RequestParam(name = "name", defaultValue = "JiangWen") String name) {
        HelloWorldService.HelloRequest request = HelloWorldService.HelloRequest.newBuilder()
                .setName(name).build();
        HelloWorldService.HelloResponse response = configuration.getStub().sayHello(request);
        log.info("Server response received: [{}]", response.getMessage());
        return response.getMessage();
    }
}

Client configuration creates the channel and stub:

@Slf4j
@Component
public class GrpcClientConfiguration {
    @Value("${server-host}") private String host;
    @Value("${server-port}") private int port;
    private ManagedChannel channel;
    private HelloWorldGrpc.HelloWorldBlockingStub stub;
    public void start() {
        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
        stub = HelloWorldGrpc.newBlockingStub(channel);
        log.info("gRPC client started, server address: {}:{}", host, port);
    }
    public void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); }
    public HelloWorldGrpc.HelloWorldBlockingStub getStub() { return stub; }
}

Register the client lifecycle with another CommandLineRunner that starts the client and adds a shutdown hook.

Docker Images

Build Dockerfiles for server and client, exposing ports 18080 (HTTP) and 18888 (gRPC) for the server, and 19090 for the client.

# Server Dockerfile
FROM openjdk:8u121-jdk
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
ADD /target/spring-boot-istio-server-0.0.1-SNAPSHOT.jar /
ENV SERVER_PORT="18080"
ENTRYPOINT java -jar /spring-boot-istio-server-0.0.1-SNAPSHOT.jar
# Client Dockerfile
FROM openjdk:8u121-jdk
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
ADD /target/spring-boot-istio-client-0.0.1-SNAPSHOT.jar /
ENV GRPC_SERVER_HOST="spring-boot-istio-server"
ENV GRPC_SERVER_PORT="18888"
ENTRYPOINT java -jar /spring-boot-istio-client-0.0.1-SNAPSHOT.jar \
  --server-host=$GRPC_SERVER_HOST \
  --server-port=$GRPC_SERVER_PORT

Kubernetes & Istio Deployment

Create Service and Deployment YAMLs for both server and client, exposing the necessary ports, and apply them with kubectl apply -f . Then define an Istio Gateway and VirtualService to route /hello to the client service.

# Server Service & Deployment (excerpt)
apiVersion: v1
kind: Service
metadata:
  name: spring-boot-istio-server
spec:
  ports:
  - name: http
    port: 18080
    targetPort: 18080
  - name: grpc
    port: 18888
    targetPort: 18888
  selector:
    app: spring-boot-istio-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-istio-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-boot-istio-server
  template:
    metadata:
      labels:
        app: spring-boot-istio-server
    spec:
      containers:
      - name: spring-boot-istio-server
        image: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-server:0.0.1-SNAPSHOT
        ports:
        - name: http
          containerPort: 18080
        - name: grpc
          containerPort: 18888

Similar YAML is provided for the client.

# Istio Gateway & VirtualService (excerpt)
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: spring-boot-istio-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: spring-boot-istio
spec:
  hosts:
  - "*"
  gateways:
  - spring-boot-istio-gateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: spring-boot-istio-client
        port:
          number: 19090

After deploying, obtain the Istio ingress NodePort, set GATEWAY_URL , and test with curl http://${GATEWAY_URL}/hello . A successful response like Hello, JiangWen. This message comes from gRPC. confirms the deployment.

All source code is available on GitHub at https://github.com/WenDev/spring-boot-istio-demo .

DockermicroservicesKubernetesgRPCSpring Bootistioservice mesh
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.