Building a Spring Boot Microservice with gRPC and Istio: A Step‑by‑Step Guide
This article explains how to replace Spring Cloud with Istio by creating a simple Spring Boot microservice that uses gRPC for communication, packaging it with Docker, deploying it on Kubernetes, and exposing it through an Istio gateway, complete with code examples and deployment instructions.
In this tutorial the author, a senior architect, describes why the traditional Spring Cloud stack can be cumbersome and how Istio provides a zero‑intrusion service‑mesh alternative that works across multiple languages and runtimes.
After outlining the drawbacks of Spring Cloud (tight coupling of business code with governance logic, version incompatibility, limited multi‑language support, and conflicts with Kubernetes), the article motivates the switch to Istio and gRPC for lightweight, high‑performance RPC communication.
The project is organized as a Maven multi‑module build with three modules: spring-boot-istio-api, spring-boot-istio-server, and spring-boot-istio-client. The parent pom.xml includes the Spring Boot starter parent and the grpc-all dependency:
<project ...>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>spring-boot-istio-api</module>
<module>spring-boot-istio-server</module>
<module>spring-boot-istio-client</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.28.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>The hello.proto file defines a simple service that echoes a string:
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 side implementation extends HelloWorldGrpc.HelloWorldImplBase and returns a formatted greeting:
@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());
}
}The server is started from a Spring bean that builds a Server on the configured port and registers a shutdown hook:
@Slf4j
@Component
public class GrpcServerConfiguration {
@Autowired
private HelloServiceImpl service;
@Value("${grpc.server-port}")
private int port;
private Server server;
public void start() throws IOException {
server = ServerBuilder.forPort(port).addService(service).build().start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try { stop(); } catch (Exception e) { }
}));
}
private void stop() { if (server != null) server.shutdown(); }
public void block() throws InterruptedException { if (server != null) server.awaitTermination(); }
}A CommandLineRunner bean invokes configuration.start() and configuration.block() so the gRPC server runs together with the Spring Boot application.
Client side code creates a stub, sends a request, and logs the response:
@RestController
@Slf4j
public class HelloController {
@Autowired
private 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();
}
}The client configuration builds a ManagedChannel to the server address and creates a blocking 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);
log.info("gRPC client shut down successfully.");
}
public HelloWorldGrpc.HelloWorldBlockingStub getStub() { return stub; }
}A matching CommandLineRunner starts and shuts down the client when the application terminates.
Dockerfiles for the server and client are based on openjdk:8u121-jdk, set the timezone, copy the built jar, expose the appropriate ports (18080 for HTTP, 18888 for gRPC on the server; 19090 for the client), and define the entrypoint:
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.jarSimilarly for the client, the entrypoint passes --server-host and --server-port arguments.
Kubernetes manifests define a Service exposing both HTTP and gRPC ports and a Deployment with the corresponding container image, pull policy, and port definitions. The client and server each have their own Service and Deployment.
Istio resources include a Gateway that listens on port 80 and a VirtualService that routes the /hello path to the client service on port 19090:
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: 19090After applying the manifests, the author sets environment variables to obtain the NodePort of the Istio ingress gateway (e.g.,
export INGRESS_PORT=$(kubectl -n istio-system get svc istio‑ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')) and tests the end‑to‑end flow with: curl -s http://${GATEWAY_URL}/hello The response Hello, JiangWen. This message comes from gRPC. confirms that the client successfully called the server through Istio, demonstrating a clean separation of business logic and service‑mesh concerns.
All source code is available on GitHub at https://github.com/WenDev/spring-boot-istio-demo .
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
