How to Use gRPC and REST Together in Spring Boot
Microservices architecture often requires flexibility to cater to both internal and external client needs. While gRPC offers unparalleled advantages for fast, efficient communication between microservices, REST remains a versatile option with near-universal compatibility for external systems and third-party integrations. The good news? You don’t have to choose one over the other. By combining gRPC and REST in a Spring Boot application, you can leverage the strengths of both protocols.
This guide will explore how to use gRPC and REST together in Spring Boot, covering topics such as running APIs on different ports, sharing business logic, and seamless integration techniques. By the end of this article, you’ll have a clear understanding of how to get the best out of both worlds.
Table of Contents
- When You Need Both gRPC and REST APIs
- Running gRPC and REST on Different Ports
- Sharing Business Logic Between Layers
- Converting Protobuf Messages to DTOs
- Using gRPC for Internal and REST for External APIs
- Implementing a REST Controller Calling a gRPC Client
- Creating a Dockerfile for Dual Protocol Support
- Kubernetes YAML Example for gRPC and REST
- Integrating Swagger/OpenAPI with gRPC Reflection
- FAQs
- Summary
When You Need Both gRPC and REST APIs
There are specific scenarios where using both gRPC and REST together is beneficial:
- Internal vs. External Communication
- gRPC is optimized for high-speed, low-latency communication between microservices.
- REST excels in providing browser compatibility and simplified connectivity for external users or third-party systems.
- Backward Compatibility
Enterprises transitioning to gRPC can retain their existing REST endpoints for legacy clients while leveraging gRPC for new microservices.
- Client Constraints
gRPC is not natively supported in browsers, making REST essential for public-facing web applications unless you use grpc-web.
By combining the two protocols, you adopt a hybrid approach that balances efficiency with accessibility.
Running gRPC and REST on Different Ports
Spring Boot makes it easy to configure both gRPC and REST APIs to run on separate ports to avoid conflicts.
Configuration Example:
- Define your REST port in
application.yml
:
server: port: 8080
- Configure your gRPC port using a library like
grpc-spring-boot-starter
:
grpc: server: port: 9090
Running gRPC and REST on different ports alongside a single business application architecture ensures operational independence without affecting performance.
See grpc-spring-boot-starter documentation
Sharing Business Logic Between Layers
Instead of duplicating functionality across gRPC and REST implementations, centralize your business logic into reusable service layers.
Example:
Create a shared service:
@Service public class UserService { public User getUserById(String id) { // Business logic to retrieve user return new User(id, "John Doe", "[email protected]"); } }
Use this service in both your REST and gRPC controllers:
@RestController public class UserRestController { @Autowired private UserService userService; @GetMapping("/api/users/{id}") public User getUser(@PathVariable String id) { return userService.getUserById(id); } } @GrpcService public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase { @Override public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) { User user = userService.getUserById(request.getId()); UserResponse response = UserResponse.newBuilder() .setId(user.getId()) .setName(user.getName()) .setEmail(user.getEmail()) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }
This approach minimizes code duplication and ensures consistent behavior across both protocols.
Converting Protobuf Messages to DTOs
Protobuf messages generated by gRPC’s .proto
files often need to be converted into DTOs (Data Transfer Objects) used by REST APIs.
Example Converter:
public class UserConverter { public static User convertProtobufToDTO(UserResponse protobuf) { return new User(protobuf.getId(), protobuf.getName(), protobuf.getEmail()); } public static UserResponse convertDTOToProtobuf(User dto) { return UserResponse.newBuilder() .setId(dto.getId()) .setName(dto.getName()) .setEmail(dto.getEmail()) .build(); } }
Maintain a clear separation between gRPC and REST layers by channeling conversions through such utility classes.
Learn about Protobuf’s Java classes
Using gRPC for Internal and REST for External APIs
A common strategy is to use gRPC for internal, microservice-to-microservice communication and REST for external, third-party communication.
- gRPC Internal APIs: High-performance, compact, and highly efficient for service-to-service interactions.
- REST External APIs: Ensures compatibility across external clients and integration flexibility.
This setup aligns with microservices’ goal of scalability while maintaining accessibility.
Read about when to choose gRPC
Implementing a REST Controller Calling a gRPC Client
You can implement a REST API that delegates tasks to a gRPC client for functionality.
Example REST Controller:
@RestController public class CombinedController { private final UserServiceGrpc.UserServiceBlockingStub userServiceStub; @Autowired public CombinedController(ManagedChannel managedChannel) { this.userServiceStub = UserServiceGrpc.newBlockingStub(managedChannel); } @GetMapping("/api/users/{id}") public User getUser(@PathVariable String id) { UserRequest request = UserRequest.newBuilder().setId(id).build(); UserResponse response = userServiceStub.getUser(request); return new User(response.getId(), response.getName(), response.getEmail()); } }
This hybrid approach bridges the gap between protocols seamlessly.
Example of combining REST and gRPC
Creating a Dockerfile for Dual Protocol Support
Build and containerize your application with support for both protocols.
Dockerfile Example:
FROM openjdk:17-jdk-alpine COPY target/spring-app.jar app.jar EXPOSE 8080 9090 ENTRYPOINT ["java", "-jar", "app.jar"]
This configuration exposes both gRPC (port 9090) and REST (port 8080) services.
Kubernetes YAML Example for gRPC and REST
Deploy your application with Kubernetes by exposing both endpoints through a single deployment.
Example YAML:
apiVersion: apps/v1 kind: Deployment metadata: name: app-deployment spec: replicas: 2 selector: matchLabels: app: spring-app template: metadata: labels: app: spring-app spec: containers: - name: spring-app image: spring-app-image ports: - containerPort: 8080 # REST - containerPort: 9090 # gRPC
Learn Kubernetes deployment basics
Integrating Swagger/OpenAPI with gRPC Reflection
Rest APIs can use Swagger/OpenAPI for documentation, and gRPC can provide reflection for introspection tools.
- Enable Swagger for REST:
- Add
springdoc-openapi-ui
dependency. - Access REST API docs at
/swagger-ui.html
.
- Add
- Use gRPC Reflection:
- Add
grpc-java
reflection library. - Enable reflection service to support gRPC clients like grpcurl.
- Add
Swagger Integration | gRPC Reflection
FAQs
Why combine gRPC and REST?
It allows you to leverage the performance of gRPC while keeping REST for compatibility with external systems.
How do I test gRPC and REST together?
Use tools like Postman
for REST APIs and grpcurl
for gRPC services.
Can I run gRPC and REST on the same port?
No, they require separate configurations and ports due to protocol differences.
What are the deployment challenges?
Ensure proper firewall configurations to expose both gRPC and REST ports.
Summary
Using gRPC and REST together in Spring Boot unlocks a balanced approach to high-performance internal communication and universally compatible external APIs. By running APIs on separate ports, sharing business logic, leveraging Protobuf-based DTOs, and integrating tools like Docker and Kubernetes, you can build an architecture optimized for flexibility and scalability. Use this guide as your blueprint to implement both protocols cohesively, and feel free to explore the external documentation referenced here for deeper insights!