gRPC Testing with Java: Comprehensive Guide

gRPC testing involves verifying both client and server implementations, testing various scenarios, and ensuring the reliability of your gRPC services.


Core Concepts

What is gRPC Testing?

  • Unit testing of gRPC services and clients
  • Integration testing of gRPC communication
  • Testing different gRPC call types (Unary, Streaming)
  • Mocking gRPC services for isolated testing
  • Performance and load testing

Key Testing Strategies:

  • In-process testing: Running gRPC server in test
  • Mock servers: Simulating gRPC service behavior
  • Test containers: Using real gRPC servers in containers
  • Wire mocking: Intercepting gRPC calls

Dependencies and Setup

Maven Dependencies
<properties> <grpc.version>1.59.0</grpc.version> <protobuf.version>3.25.1</protobuf.version> <grpc-spring-boot.version>2.15.0.RELEASE</grpc-spring-boot.version> <junit.version>5.9.2</junit.version> <mockito.version>5.5.0</mockito.version> <testcontainers.version>1.19.1</testcontainers.version> </properties> <dependencies> <!-- gRPC --> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-core</artifactId> <version>${grpc.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>${grpc.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>${grpc.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>${grpc.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-testing</artifactId> <version>${grpc.version}</version> <scope>test</scope> </dependency> <!-- Protobuf --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> <version>${protobuf.version}</version> </dependency> <!-- Spring Boot gRPC --> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-spring-boot-starter</artifactId> <version>${grpc-spring-boot.version}</version> </dependency> <!-- Testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <!-- Test Containers --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency> <!-- Assertions --> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> <scope>test</scope> </dependency> <!-- Awaitility --> <dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Protocol Buffer Definitions
// src/main/proto/user_service.proto syntax = "proto3"; package com.example.grpc; option java_package = "com.example.grpc"; option java_multiple_files = true; // User service definition service UserService { // Unary RPC rpc GetUser (GetUserRequest) returns (UserResponse); rpc CreateUser (CreateUserRequest) returns (UserResponse); rpc UpdateUser (UpdateUserRequest) returns (UserResponse); rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse); // Server streaming RPC rpc ListUsers (ListUsersRequest) returns (stream UserResponse); // Client streaming RPC rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse); // Bidirectional streaming RPC rpc Chat (stream ChatMessage) returns (stream ChatMessage); } // Request/Response messages message GetUserRequest { string user_id = 1; } message CreateUserRequest { string name = 1; string email = 2; int32 age = 3; } message UpdateUserRequest { string user_id = 1; string name = 2; string email = 3; int32 age = 4; } message DeleteUserRequest { string user_id = 1; } message DeleteUserResponse { bool success = 1; string message = 2; } message ListUsersRequest { int32 page_size = 1; string page_token = 2; } message CreateUsersResponse { int32 created_count = 1; repeated string user_ids = 2; } message UserResponse { string user_id = 1; string name = 2; string email = 3; int32 age = 4; string created_at = 5; string updated_at = 6; } message ChatMessage { string user_id = 1; string message = 2; int64 timestamp = 3; }

gRPC Service Implementation

1. User Service Implementation
package com.example.grpc.service; import com.example.grpc.*; import io.grpc.Status; import io.grpc.stub.StreamObserver; import org.lognet.springboot.grpc.GRpcService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @GRpcService public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase { private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); private final Map<String, UserResponse> userStore = new ConcurrentHashMap<>(); private final AtomicInteger userIdCounter = new AtomicInteger(1); @Override public void getUser(GetUserRequest request, StreamObserver<UserResponse> responseObserver) { logger.info("GetUser request for ID: {}", request.getUserId()); String userId = request.getUserId(); UserResponse user = userStore.get(userId); if (user == null) { responseObserver.onError(Status.NOT_FOUND .withDescription("User not found: " + userId) .asRuntimeException()); return; } responseObserver.onNext(user); responseObserver.onCompleted(); } @Override public void createUser(CreateUserRequest request, StreamObserver<UserResponse> responseObserver) { logger.info("CreateUser request for: {}", request.getEmail()); try { // Validate request if (request.getName().isEmpty() || request.getEmail().isEmpty()) { responseObserver.onError(Status.INVALID_ARGUMENT .withDescription("Name and email are required") .asRuntimeException()); return; } // Check if user already exists boolean userExists = userStore.values().stream() .anyMatch(user -> user.getEmail().equals(request.getEmail())); if (userExists) { responseObserver.onError(Status.ALREADY_EXISTS .withDescription("User with email already exists: " + request.getEmail()) .asRuntimeException()); return; } // Create user String userId = "user_" + userIdCounter.getAndIncrement(); UserResponse user = UserResponse.newBuilder() .setUserId(userId) .setName(request.getName()) .setEmail(request.getEmail()) .setAge(request.getAge()) .setCreatedAt(String.valueOf(System.currentTimeMillis())) .setUpdatedAt(String.valueOf(System.currentTimeMillis())) .build(); userStore.put(userId, user); logger.info("User created successfully: {}", userId); responseObserver.onNext(user); responseObserver.onCompleted(); } catch (Exception e) { logger.error("Error creating user", e); responseObserver.onError(Status.INTERNAL .withDescription("Failed to create user") .withCause(e) .asRuntimeException()); } } @Override public void updateUser(UpdateUserRequest request, StreamObserver<UserResponse> responseObserver) { logger.info("UpdateUser request for ID: {}", request.getUserId()); try { String userId = request.getUserId(); UserResponse existingUser = userStore.get(userId); if (existingUser == null) { responseObserver.onError(Status.NOT_FOUND .withDescription("User not found: " + userId) .asRuntimeException()); return; } // Update user UserResponse updatedUser = UserResponse.newBuilder(existingUser) .setName(request.getName()) .setEmail(request.getEmail()) .setAge(request.getAge()) .setUpdatedAt(String.valueOf(System.currentTimeMillis())) .build(); userStore.put(userId, updatedUser); logger.info("User updated successfully: {}", userId); responseObserver.onNext(updatedUser); responseObserver.onCompleted(); } catch (Exception e) { logger.error("Error updating user", e); responseObserver.onError(Status.INTERNAL .withDescription("Failed to update user") .withCause(e) .asRuntimeException()); } } @Override public void deleteUser(DeleteUserRequest request, StreamObserver<DeleteUserResponse> responseObserver) { logger.info("DeleteUser request for ID: {}", request.getUserId()); String userId = request.getUserId(); UserResponse removedUser = userStore.remove(userId); DeleteUserResponse response = DeleteUserResponse.newBuilder() .setSuccess(removedUser != null) .setMessage(removedUser != null ? "User deleted successfully" : "User not found") .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } @Override public void listUsers(ListUsersRequest request, StreamObserver<UserResponse> responseObserver) { logger.info("ListUsers request - page size: {}", request.getPageSize()); try { int pageSize = request.getPageSize() > 0 ? request.getPageSize() : 10; List<UserResponse> users = new ArrayList<>(userStore.values()); // Simulate pagination int endIndex = Math.min(pageSize, users.size()); List<UserResponse> pageUsers = users.subList(0, endIndex); for (UserResponse user : pageUsers) { responseObserver.onNext(user); // Simulate some processing time Thread.sleep(100); } responseObserver.onCompleted(); logger.info("ListUsers completed, sent {} users", pageUsers.size()); } catch (Exception e) { logger.error("Error listing users", e); responseObserver.onError(Status.INTERNAL .withDescription("Failed to list users") .withCause(e) .asRuntimeException()); } } @Override public StreamObserver<CreateUserRequest> createUsers(StreamObserver<CreateUsersResponse> responseObserver) { logger.info("CreateUsers streaming request started"); return new StreamObserver<CreateUserRequest>() { private final List<String> createdUserIds = new ArrayList<>(); private int processedCount = 0; @Override public void onNext(CreateUserRequest request) { logger.debug("Processing streaming user creation: {}", request.getEmail()); try { // Create user String userId = "user_" + userIdCounter.getAndIncrement(); UserResponse user = UserResponse.newBuilder() .setUserId(userId) .setName(request.getName()) .setEmail(request.getEmail()) .setAge(request.getAge()) .setCreatedAt(String.valueOf(System.currentTimeMillis())) .setUpdatedAt(String.valueOf(System.currentTimeMillis())) .build(); userStore.put(userId, user); createdUserIds.add(userId); processedCount++; logger.debug("Streaming user created: {}", userId); } catch (Exception e) { logger.error("Error in streaming user creation", e); // Continue processing other requests } } @Override public void onError(Throwable t) { logger.error("Error in CreateUsers stream", t); } @Override public void onCompleted() { logger.info("CreateUsers streaming completed, processed {} users", processedCount); CreateUsersResponse response = CreateUsersResponse.newBuilder() .setCreatedCount(createdUserIds.size()) .addAllUserIds(createdUserIds) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }; } @Override public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) { logger.info("Chat bidirectional streaming started"); return new StreamObserver<ChatMessage>() { @Override public void onNext(ChatMessage message) { logger.debug("Received chat message from {}: {}", message.getUserId(), message.getMessage()); // Echo the message back with timestamp ChatMessage response = ChatMessage.newBuilder(message) .setTimestamp(System.currentTimeMillis()) .build(); responseObserver.onNext(response); } @Override public void onError(Throwable t) { logger.error("Error in chat stream", t); } @Override public void onCompleted() { logger.info("Chat streaming completed"); responseObserver.onCompleted(); } }; } }
2. gRPC Client Service
package com.example.grpc.client; import com.example.grpc.*; import io.grpc.Channel; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @Service public class UserServiceClient { private static final Logger logger = LoggerFactory.getLogger(UserServiceClient.class); private final UserServiceGrpc.UserServiceBlockingStub blockingStub; private final UserServiceGrpc.UserServiceStub asyncStub; public UserServiceClient(Channel channel) { this.blockingStub = UserServiceGrpc.newBlockingStub(channel); this.asyncStub = UserServiceGrpc.newStub(channel); } // Unary RPC methods public UserResponse getUser(String userId) { logger.info("Getting user: {}", userId); try { GetUserRequest request = GetUserRequest.newBuilder() .setUserId(userId) .build(); return blockingStub.getUser(request); } catch (StatusRuntimeException e) { logger.error("RPC failed: {}", e.getStatus()); throw new GrpcClientException("Failed to get user", e); } } public UserResponse createUser(String name, String email, int age) { logger.info("Creating user: {}", email); try { CreateUserRequest request = CreateUserRequest.newBuilder() .setName(name) .setEmail(email) .setAge(age) .build(); return blockingStub.createUser(request); } catch (StatusRuntimeException e) { logger.error("RPC failed: {}", e.getStatus()); throw new GrpcClientException("Failed to create user", e); } } // Server streaming RPC public List<UserResponse> listUsers(int pageSize) { logger.info("Listing users with page size: {}", pageSize); try { ListUsersRequest request = ListUsersRequest.newBuilder() .setPageSize(pageSize) .build(); List<UserResponse> users = new ArrayList<>(); blockingStub.listUsers(request).forEachRemaining(users::add); logger.info("Retrieved {} users", users.size()); return users; } catch (StatusRuntimeException e) { logger.error("RPC failed: {}", e.getStatus()); throw new GrpcClientException("Failed to list users", e); } } // Client streaming RPC public CreateUsersResponse createUsers(List<CreateUserRequest> userRequests) { logger.info("Creating {} users via streaming", userRequests.size()); CountDownLatch finishLatch = new CountDownLatch(1); AtomicReference<CreateUsersResponse> responseRef = new AtomicReference<>(); AtomicReference<Throwable> errorRef = new AtomicReference<>(); StreamObserver<CreateUserRequest> requestObserver = asyncStub.createUsers(new StreamObserver<CreateUsersResponse>() { @Override public void onNext(CreateUsersResponse response) { responseRef.set(response); } @Override public void onError(Throwable t) { errorRef.set(t); finishLatch.countDown(); } @Override public void onCompleted() { finishLatch.countDown(); } }); try { // Send all requests for (CreateUserRequest request : userRequests) { requestObserver.onNext(request); } requestObserver.onCompleted(); // Wait for completion if (!finishLatch.await(1, TimeUnit.MINUTES)) { throw new GrpcClientException("CreateUsers timed out"); } // Check for errors if (errorRef.get() != null) { throw new GrpcClientException("CreateUsers failed", errorRef.get()); } return responseRef.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new GrpcClientException("CreateUsers interrupted", e); } } // Bidirectional streaming RPC public List<ChatMessage> chat(List<ChatMessage> messages) { logger.info("Starting chat with {} messages", messages.size()); CountDownLatch finishLatch = new CountDownLatch(1); List<ChatMessage> responses = new ArrayList<>(); AtomicReference<Throwable> errorRef = new AtomicReference<>(); StreamObserver<ChatMessage> requestObserver = asyncStub.chat(new StreamObserver<ChatMessage>() { @Override public void onNext(ChatMessage message) { responses.add(message); } @Override public void onError(Throwable t) { errorRef.set(t); finishLatch.countDown(); } @Override public void onCompleted() { finishLatch.countDown(); } }); try { // Send all messages for (ChatMessage message : messages) { requestObserver.onNext(message); } requestObserver.onCompleted(); // Wait for completion if (!finishLatch.await(30, TimeUnit.SECONDS)) { throw new GrpcClientException("Chat timed out"); } // Check for errors if (errorRef.get() != null) { throw new GrpcClientException("Chat failed", errorRef.get()); } return responses; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new GrpcClientException("Chat interrupted", e); } } } class GrpcClientException extends RuntimeException { public GrpcClientException(String message) { super(message); } public GrpcClientException(String message, Throwable cause) { super(message, cause); } }

gRPC Testing Framework

1. Base gRPC Test Configuration
package com.example.grpc.test; import io.grpc.*; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.concurrent.TimeUnit; /** * Base class for gRPC in-process testing */ public abstract class GrpcTestBase { private static final Logger logger = LoggerFactory.getLogger(GrpcTestBase.class); protected Server server; protected ManagedChannel channel; protected String serverName; @BeforeEach void setUp() throws IOException { serverName = InProcessServerBuilder.generateName(); // Create server server = InProcessServerBuilder.forName(serverName) .directExecutor() .addService(createService()) .build() .start(); // Create channel channel = InProcessChannelBuilder.forName(serverName) .directExecutor() .build(); logger.info("gRPC in-process server started: {}", serverName); } @AfterEach void tearDown() throws InterruptedException { if (channel != null) { channel.shutdown(); if (!channel.awaitTermination(5, TimeUnit.SECONDS)) { channel.shutdownNow(); } } if (server != null) { server.shutdown(); if (!server.awaitTermination(5, TimeUnit.SECONDS)) { server.shutdownNow(); } } logger.info("gRPC in-process server stopped: {}", serverName); } /** * Implement this method to provide the gRPC service for testing */ protected abstract BindableService createService(); }
2. Unit Tests for gRPC Service
package com.example.grpc.service; import com.example.grpc.*; import com.example.grpc.test.GrpcTestBase; import io.grpc.BindableService; import io.grpc.Status; import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class UserServiceTest extends GrpcTestBase { private UserServiceGrpc.UserServiceBlockingStub blockingStub; private UserServiceImpl userService; @Override protected BindableService createService() { userService = new UserServiceImpl(); return userService; } @BeforeEach void setUpStub() { blockingStub = UserServiceGrpc.newBlockingStub(channel); } @Test void testGetUser_Success() { // Given - create a user first CreateUserRequest createRequest = CreateUserRequest.newBuilder() .setName("John Doe") .setEmail("[email protected]") .setAge(30) .build(); UserResponse createdUser = blockingStub.createUser(createRequest); // When GetUserRequest request = GetUserRequest.newBuilder() .setUserId(createdUser.getUserId()) .build(); UserResponse response = blockingStub.getUser(request); // Then assertThat(response).isNotNull(); assertThat(response.getUserId()).isEqualTo(createdUser.getUserId()); assertThat(response.getName()).isEqualTo("John Doe"); assertThat(response.getEmail()).isEqualTo("[email protected]"); assertThat(response.getAge()).isEqualTo(30); } @Test void testGetUser_NotFound() { // Given GetUserRequest request = GetUserRequest.newBuilder() .setUserId("non-existent-id") .build(); // When / Then StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.getUser(request), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.NOT_FOUND.getCode()); assertThat(exception.getStatus().getDescription()).contains("User not found"); } @Test void testCreateUser_Success() { // Given CreateUserRequest request = CreateUserRequest.newBuilder() .setName("Jane Smith") .setEmail("[email protected]") .setAge(25) .build(); // When UserResponse response = blockingStub.createUser(request); // Then assertThat(response).isNotNull(); assertThat(response.getUserId()).isNotBlank(); assertThat(response.getName()).isEqualTo("Jane Smith"); assertThat(response.getEmail()).isEqualTo("[email protected]"); assertThat(response.getAge()).isEqualTo(25); assertThat(response.getCreatedAt()).isNotBlank(); assertThat(response.getUpdatedAt()).isNotBlank(); } @Test void testCreateUser_ValidationFailure() { // Given - missing required fields CreateUserRequest request = CreateUserRequest.newBuilder() .setAge(25) .build(); // When / Then StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.createUser(request), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.INVALID_ARGUMENT.getCode()); assertThat(exception.getStatus().getDescription()).contains("Name and email are required"); } @Test void testCreateUser_DuplicateEmail() { // Given - create first user CreateUserRequest firstRequest = CreateUserRequest.newBuilder() .setName("First User") .setEmail("[email protected]") .setAge(30) .build(); blockingStub.createUser(firstRequest); // When / Then - try to create second user with same email CreateUserRequest secondRequest = CreateUserRequest.newBuilder() .setName("Second User") .setEmail("[email protected]") .setAge(35) .build(); StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.createUser(secondRequest), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.ALREADY_EXISTS.getCode()); assertThat(exception.getStatus().getDescription()).contains("already exists"); } @Test void testUpdateUser_Success() { // Given - create a user first CreateUserRequest createRequest = CreateUserRequest.newBuilder() .setName("Original Name") .setEmail("[email protected]") .setAge(30) .build(); UserResponse createdUser = blockingStub.createUser(createRequest); // When - update the user UpdateUserRequest updateRequest = UpdateUserRequest.newBuilder() .setUserId(createdUser.getUserId()) .setName("Updated Name") .setEmail("[email protected]") .setAge(35) .build(); UserResponse updatedUser = blockingStub.updateUser(updateRequest); // Then assertThat(updatedUser).isNotNull(); assertThat(updatedUser.getUserId()).isEqualTo(createdUser.getUserId()); assertThat(updatedUser.getName()).isEqualTo("Updated Name"); assertThat(updatedUser.getEmail()).isEqualTo("[email protected]"); assertThat(updatedUser.getAge()).isEqualTo(35); assertThat(updatedUser.getUpdatedAt()).isNotEqualTo(createdUser.getUpdatedAt()); } @Test void testDeleteUser_Success() { // Given - create a user first CreateUserRequest createRequest = CreateUserRequest.newBuilder() .setName("To Delete") .setEmail("[email protected]") .setAge(30) .build(); UserResponse createdUser = blockingStub.createUser(createRequest); // When DeleteUserRequest deleteRequest = DeleteUserRequest.newBuilder() .setUserId(createdUser.getUserId()) .build(); DeleteUserResponse response = blockingStub.deleteUser(deleteRequest); // Then assertThat(response.getSuccess()).isTrue(); assertThat(response.getMessage()).contains("deleted successfully"); // Verify user is actually deleted GetUserRequest getRequest = GetUserRequest.newBuilder() .setUserId(createdUser.getUserId()) .build(); StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.getUser(getRequest), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.NOT_FOUND.getCode()); } @Test void testDeleteUser_NotFound() { // Given DeleteUserRequest request = DeleteUserRequest.newBuilder() .setUserId("non-existent-id") .build(); // When DeleteUserResponse response = blockingStub.deleteUser(request); // Then assertThat(response.getSuccess()).isFalse(); assertThat(response.getMessage()).contains("not found"); } @Test void testListUsers_ServerStreaming() { // Given - create multiple users for (int i = 1; i <= 5; i++) { CreateUserRequest request = CreateUserRequest.newBuilder() .setName("User " + i) .setEmail("user" + i + "@example.com") .setAge(20 + i) .build(); blockingStub.createUser(request); } // When ListUsersRequest request = ListUsersRequest.newBuilder() .setPageSize(3) .build(); List<UserResponse> users = new ArrayList<>(); blockingStub.listUsers(request).forEachRemaining(users::add); // Then assertThat(users).hasSize(3); assertThat(users).allSatisfy(user -> { assertThat(user.getUserId()).isNotBlank(); assertThat(user.getName()).isNotBlank(); assertThat(user.getEmail()).isNotBlank(); }); } }
3. Streaming Tests
package com.example.grpc.service; import com.example.grpc.*; import com.example.grpc.test.GrpcTestBase; import io.grpc.BindableService; import io.grpc.stub.StreamObserver; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class UserServiceStreamingTest extends GrpcTestBase { private UserServiceGrpc.UserServiceStub asyncStub; @Override protected BindableService createService() { return new UserServiceImpl(); } @BeforeEach void setUpStub() { asyncStub = UserServiceGrpc.newStub(channel); } @Test void testCreateUsers_ClientStreaming() throws InterruptedException { // Given List<CreateUserRequest> requests = List.of( CreateUserRequest.newBuilder() .setName("User 1") .setEmail("[email protected]") .setAge(25) .build(), CreateUserRequest.newBuilder() .setName("User 2") .setEmail("[email protected]") .setAge(30) .build(), CreateUserRequest.newBuilder() .setName("User 3") .setEmail("[email protected]") .setAge(35) .build() ); CountDownLatch finishLatch = new CountDownLatch(1); AtomicReference<CreateUsersResponse> responseRef = new AtomicReference<>(); AtomicReference<Throwable> errorRef = new AtomicReference<>(); // When StreamObserver<CreateUserRequest> requestObserver = asyncStub.createUsers(new StreamObserver<CreateUsersResponse>() { @Override public void onNext(CreateUsersResponse response) { responseRef.set(response); } @Override public void onError(Throwable t) { errorRef.set(t); finishLatch.countDown(); } @Override public void onCompleted() { finishLatch.countDown(); } }); // Send all requests for (CreateUserRequest request : requests) { requestObserver.onNext(request); } requestObserver.onCompleted(); // Wait for completion assertTrue(finishLatch.await(5, TimeUnit.SECONDS), "Operation timed out"); // Then assertThat(errorRef.get()).isNull(); assertThat(responseRef.get()).isNotNull(); assertThat(responseRef.get().getCreatedCount()).isEqualTo(3); assertThat(responseRef.get().getUserIdsList()).hasSize(3); } @Test void testChat_BidirectionalStreaming() throws InterruptedException { // Given List<ChatMessage> messagesToSend = List.of( ChatMessage.newBuilder() .setUserId("user1") .setMessage("Hello") .setTimestamp(System.currentTimeMillis()) .build(), ChatMessage.newBuilder() .setUserId("user1") .setMessage("How are you?") .setTimestamp(System.currentTimeMillis()) .build(), ChatMessage.newBuilder() .setUserId("user1") .setMessage("Goodbye") .setTimestamp(System.currentTimeMillis()) .build() ); CountDownLatch finishLatch = new CountDownLatch(1); List<ChatMessage> receivedMessages = new ArrayList<>(); AtomicReference<Throwable> errorRef = new AtomicReference<>(); // When StreamObserver<ChatMessage> requestObserver = asyncStub.chat(new StreamObserver<ChatMessage>() { @Override public void onNext(ChatMessage message) { receivedMessages.add(message); } @Override public void onError(Throwable t) { errorRef.set(t); finishLatch.countDown(); } @Override public void onCompleted() { finishLatch.countDown(); } }); // Send all messages for (ChatMessage message : messagesToSend) { requestObserver.onNext(message); } requestObserver.onCompleted(); // Wait for completion assertTrue(finishLatch.await(5, TimeUnit.SECONDS), "Operation timed out"); // Then assertThat(errorRef.get()).isNull(); assertThat(receivedMessages).hasSize(3); // Verify responses match sent messages for (int i = 0; i < messagesToSend.size(); i++) { ChatMessage sent = messagesToSend.get(i); ChatMessage received = receivedMessages.get(i); assertThat(received.getUserId()).isEqualTo(sent.getUserId()); assertThat(received.getMessage()).isEqualTo(sent.getMessage()); assertThat(received.getTimestamp()).isGreaterThan(0); } } }
4. gRPC Client Tests
package com.example.grpc.client; import com.example.grpc.*; import com.example.grpc.test.GrpcTestBase; import io.grpc.BindableService; import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import java.util.List; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class UserServiceClientTest extends GrpcTestBase { private UserServiceClient client; private UserServiceGrpc.UserServiceBlockingStub blockingStub; @Override protected BindableService createService() { return new UserServiceImpl(); } @BeforeEach void setUpClient() { client = new UserServiceClient(channel); blockingStub = UserServiceGrpc.newBlockingStub(channel); } @Test void testGetUser_Success() { // Given - create a user via blocking stub CreateUserRequest createRequest = CreateUserRequest.newBuilder() .setName("Test User") .setEmail("[email protected]") .setAge(30) .build(); UserResponse createdUser = blockingStub.createUser(createRequest); // When UserResponse response = client.getUser(createdUser.getUserId()); // Then assertThat(response).isNotNull(); assertThat(response.getUserId()).isEqualTo(createdUser.getUserId()); assertThat(response.getName()).isEqualTo("Test User"); assertThat(response.getEmail()).isEqualTo("[email protected]"); } @Test void testGetUser_NotFound() { // When / Then GrpcClientException exception = catchThrowableOfType( () -> client.getUser("non-existent-id"), GrpcClientException.class ); assertThat(exception).hasMessageContaining("Failed to get user"); assertThat(exception.getCause()).isInstanceOf(StatusRuntimeException.class); } @Test void testCreateUser_Success() { // When UserResponse response = client.createUser("New User", "[email protected]", 25); // Then assertThat(response).isNotNull(); assertThat(response.getName()).isEqualTo("New User"); assertThat(response.getEmail()).isEqualTo("[email protected]"); assertThat(response.getAge()).isEqualTo(25); } @Test void testListUsers_Success() { // Given - create multiple users for (int i = 1; i <= 5; i++) { blockingStub.createUser(CreateUserRequest.newBuilder() .setName("User " + i) .setEmail("user" + i + "@example.com") .setAge(20 + i) .build()); } // When List<UserResponse> users = client.listUsers(3); // Then assertThat(users).hasSize(3); assertThat(users).allSatisfy(user -> { assertThat(user.getUserId()).isNotBlank(); assertThat(user.getName()).isNotBlank(); assertThat(user.getEmail()).isNotBlank(); }); } @Test void testCreateUsers_ClientStreaming() { // Given List<CreateUserRequest> requests = List.of( CreateUserRequest.newBuilder() .setName("Stream User 1") .setEmail("[email protected]") .setAge(25) .build(), CreateUserRequest.newBuilder() .setName("Stream User 2") .setEmail("[email protected]") .setAge(30) .build() ); // When CreateUsersResponse response = client.createUsers(requests); // Then assertThat(response).isNotNull(); assertThat(response.getCreatedCount()).isEqualTo(2); assertThat(response.getUserIdsList()).hasSize(2); } @Test void testChat_BidirectionalStreaming() { // Given List<ChatMessage> messages = List.of( ChatMessage.newBuilder() .setUserId("test-user") .setMessage("Hello") .setTimestamp(System.currentTimeMillis()) .build(), ChatMessage.newBuilder() .setUserId("test-user") .setMessage("Test message") .setTimestamp(System.currentTimeMillis()) .build() ); // When List<ChatMessage> responses = client.chat(messages); // Then assertThat(responses).hasSize(2); assertThat(responses.get(0).getMessage()).isEqualTo("Hello"); assertThat(responses.get(1).getMessage()).isEqualTo("Test message"); } }
5. Mock Server Tests
package com.example.grpc.service; import com.example.grpc.*; import io.grpc.Status; import io.grpc.stub.StreamObserver; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.testing.GrpcCleanupRule; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.*; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UserServiceMockTest { private final UserServiceGrpc.UserServiceImplBase serviceImpl = mock(UserServiceGrpc.UserServiceImplBase.class, delegatesTo( new UserServiceGrpc.UserServiceImplBase() { // Default implementations that can be overridden by Mockito } )); @Test void testGetUserWithMock() throws IOException { // Given UserServiceGrpc.UserServiceImplBase service = mock(UserServiceGrpc.UserServiceImplBase.class); UserResponse mockResponse = UserResponse.newBuilder() .setUserId("mock-user") .setName("Mock User") .setEmail("[email protected]") .setAge(30) .setCreatedAt("123456789") .setUpdatedAt("123456789") .build(); // Set up mock behavior doAnswer(invocation -> { StreamObserver<UserResponse> responseObserver = invocation.getArgument(1); responseObserver.onNext(mockResponse); responseObserver.onCompleted(); return null; }).when(service).getUser(any(GetUserRequest.class), any(StreamObserver.class)); // Create in-process server with mock service String serverName = InProcessChannelBuilder.generateName(); GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); grpcCleanup.register(InProcessServerBuilder .forName(serverName) .directExecutor() .addService(service) .build() .start()); UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub( grpcCleanup.register(InProcessChannelBuilder .forName(serverName) .directExecutor() .build())); // When GetUserRequest request = GetUserRequest.newBuilder() .setUserId("test-user") .build(); UserResponse response = stub.getUser(request); // Then assertThat(response).isEqualTo(mockResponse); verify(service).getUser(any(GetUserRequest.class), any(StreamObserver.class)); } @Test void testGetUserErrorWithMock() throws IOException { // Given UserServiceGrpc.UserServiceImplBase service = mock(UserServiceGrpc.UserServiceImplBase.class); // Set up mock to throw error doAnswer(invocation -> { StreamObserver<UserResponse> responseObserver = invocation.getArgument(1); responseObserver.onError(Status.NOT_FOUND .withDescription("User not found") .asRuntimeException()); return null; }).when(service).getUser(any(GetUserRequest.class), any(StreamObserver.class)); // Create in-process server with mock service String serverName = InProcessChannelBuilder.generateName(); GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); grpcCleanup.register(InProcessServerBuilder .forName(serverName) .directExecutor() .addService(service) .build() .start()); UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub( grpcCleanup.register(InProcessChannelBuilder .forName(serverName) .directExecutor() .build())); // When / Then GetUserRequest request = GetUserRequest.newBuilder() .setUserId("non-existent") .build(); assertThatThrownBy(() -> stub.getUser(request)) .isInstanceOf(io.grpc.StatusRuntimeException.class) .hasMessageContaining("NOT_FOUND: User not found"); verify(service).getUser(any(GetUserRequest.class), any(StreamObserver.class)); } @Test void testListUsersStreamingWithMock() throws IOException { // Given UserServiceGrpc.UserServiceImplBase service = mock(UserServiceGrpc.UserServiceImplBase.class); List<UserResponse> mockResponses = List.of( UserResponse.newBuilder() .setUserId("user1") .setName("User One") .setEmail("[email protected]") .setAge(25) .build(), UserResponse.newBuilder() .setUserId("user2") .setName("User Two") .setEmail("[email protected]") .setAge(30) .build() ); // Set up mock behavior for streaming doAnswer(invocation -> { StreamObserver<UserResponse> responseObserver = invocation.getArgument(1); for (UserResponse response : mockResponses) { responseObserver.onNext(response); } responseObserver.onCompleted(); return null; }).when(service).listUsers(any(ListUsersRequest.class), any(StreamObserver.class)); // Create in-process server with mock service String serverName = InProcessChannelBuilder.generateName(); GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); grpcCleanup.register(InProcessServerBuilder .forName(serverName) .directExecutor() .addService(service) .build() .start()); UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub( grpcCleanup.register(InProcessChannelBuilder .forName(serverName) .directExecutor() .build())); // When ListUsersRequest request = ListUsersRequest.newBuilder() .setPageSize(10) .build(); List<UserResponse> responses = new ArrayList<>(); stub.listUsers(request).forEachRemaining(responses::add); // Then assertThat(responses).hasSize(2); assertThat(responses).containsExactlyElementsOf(mockResponses); verify(service).listUsers(any(ListUsersRequest.class), any(StreamObserver.class)); } }
6. Integration Tests with TestContainers
package com.example.grpc.integration; import com.example.grpc.UserServiceGrpc; import com.example.grpc.UserResponse; import com.example.grpc.CreateUserRequest; import com.example.grpc.GetUserRequest; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import org.junit.jupiter.api.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import static org.assertj.core.api.Assertions.assertThat; @Testcontainers class UserServiceIntegrationTest { private static final int GRPC_PORT = 6565; @Container private static final GenericContainer<?> grpcServer = new GenericContainer<>( DockerImageName.parse("my-grpc-server:latest")) .withExposedPorts(GRPC_PORT) .withEnv("SERVER_PORT", String.valueOf(GRPC_PORT)); @Test void testGrpcServerIntegration() { // Get mapped port Integer mappedPort = grpcServer.getMappedPort(GRPC_PORT); String host = grpcServer.getHost(); // Create channel to test container ManagedChannel channel = ManagedChannelBuilder .forAddress(host, mappedPort) .usePlaintext() .build(); try { UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel); // Test create user CreateUserRequest createRequest = CreateUserRequest.newBuilder() .setName("Integration User") .setEmail("[email protected]") .setAge(35) .build(); UserResponse createResponse = stub.createUser(createRequest); assertThat(createResponse).isNotNull(); assertThat(createResponse.getName()).isEqualTo("Integration User"); assertThat(createResponse.getEmail()).isEqualTo("[email protected]"); // Test get user GetUserRequest getRequest = GetUserRequest.newBuilder() .setUserId(createResponse.getUserId()) .build(); UserResponse getResponse = stub.getUser(getRequest); assertThat(getResponse).isNotNull(); assertThat(getResponse.getUserId()).isEqualTo(createResponse.getUserId()); } finally { channel.shutdown(); } } }
7. Performance and Load Testing
package com.example.grpc.performance; import com.example.grpc.*; import com.example.grpc.test.GrpcTestBase; import io.grpc.BindableService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; class UserServicePerformanceTest extends GrpcTestBase { private UserServiceGrpc.UserServiceBlockingStub blockingStub; private UserServiceGrpc.UserServiceStub asyncStub; private ExecutorService executorService; @Override protected BindableService createService() { return new UserServiceImpl(); } @BeforeEach void setUpStubs() { blockingStub = UserServiceGrpc.newBlockingStub(channel); asyncStub = UserServiceGrpc.newStub(channel); executorService = Executors.newFixedThreadPool(10); } @Test void testConcurrentUserCreations() throws InterruptedException { int numberOfRequests = 100; CountDownLatch latch = new CountDownLatch(numberOfRequests); AtomicInteger successCount = new AtomicInteger(0); AtomicInteger errorCount = new AtomicInteger(0); // When - execute concurrent requests for (int i = 0; i < numberOfRequests; i++) { final int index = i; executorService.submit(() -> { try { CreateUserRequest request = CreateUserRequest.newBuilder() .setName("User " + index) .setEmail("user" + index + "@example.com") .setAge(20 + (index % 50)) .build(); blockingStub.createUser(request); successCount.incrementAndGet(); } catch (Exception e) { errorCount.incrementAndGet(); } finally { latch.countDown(); } }); } // Then - wait for all requests to complete assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); System.out.printf("Performance test completed: %d success, %d errors%n", successCount.get(), errorCount.get()); assertThat(successCount.get()).isGreaterThan(0); // Note: Some errors are expected due to duplicate email constraints } @Test void testStreamingPerformance() throws InterruptedException { int numberOfUsers = 50; List<CreateUserRequest> requests = new ArrayList<>(); for (int i = 0; i < numberOfUsers; i++) { requests.add(CreateUserRequest.newBuilder() .setName("Stream User " + i) .setEmail("stream" + i + "@example.com") .setAge(25 + (i % 30)) .build()); } long startTime = System.currentTimeMillis(); // When - use client streaming CountDownLatch finishLatch = new CountDownLatch(1); AtomicInteger responseCount = new AtomicInteger(0); StreamObserver<CreateUserRequest> requestObserver = asyncStub.createUsers(new StreamObserver<CreateUsersResponse>() { @Override public void onNext(CreateUsersResponse response) { responseCount.set(response.getCreatedCount()); } @Override public void onError(Throwable t) { finishLatch.countDown(); } @Override public void onCompleted() { finishLatch.countDown(); } }); for (CreateUserRequest request : requests) { requestObserver.onNext(request); } requestObserver.onCompleted(); // Wait for completion assertThat(finishLatch.await(10, TimeUnit.SECONDS)).isTrue(); long endTime = System.currentTimeMillis(); long duration = endTime - startTime; System.out.printf("Streaming performance: %d users in %d ms%n", responseCount.get(), duration); assertThat(responseCount.get()).isGreaterThan(0); assertThat(duration).isLessThan(5000); // Should complete within 5 seconds } }
8. Error Handling and Resilience Tests
package com.example.grpc.error; import com.example.grpc.*; import com.example.grpc.test.GrpcTestBase; import io.grpc.BindableService; import io.grpc.Status; import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import static org.assertj.core.api.Assertions.*; class UserServiceErrorTest extends GrpcTestBase { private UserServiceGrpc.UserServiceBlockingStub blockingStub; // Service that throws various exceptions for testing static class ErrorProneUserService extends UserServiceGrpc.UserServiceImplBase { @Override public void getUser(GetUserRequest request, StreamObserver<UserResponse> responseObserver) { if ("timeout".equals(request.getUserId())) { // Simulate timeout by not responding return; } else if ("internal-error".equals(request.getUserId())) { responseObserver.onError(Status.INTERNAL .withDescription("Internal server error") .asRuntimeException()); } else if ("invalid-argument".equals(request.getUserId())) { responseObserver.onError(Status.INVALID_ARGUMENT .withDescription("Invalid user ID") .asRuntimeException()); } else { responseObserver.onError(Status.NOT_FOUND .withDescription("User not found: " + request.getUserId()) .asRuntimeException()); } } } @Override protected BindableService createService() { return new ErrorProneUserService(); } @BeforeEach void setUpStub() { blockingStub = UserServiceGrpc.newBlockingStub(channel); } @Test void testInternalError() { GetUserRequest request = GetUserRequest.newBuilder() .setUserId("internal-error") .build(); StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.getUser(request), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.INTERNAL.getCode()); assertThat(exception.getStatus().getDescription()).contains("Internal server error"); } @Test void testInvalidArgumentError() { GetUserRequest request = GetUserRequest.newBuilder() .setUserId("invalid-argument") .build(); StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.getUser(request), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.INVALID_ARGUMENT.getCode()); assertThat(exception.getStatus().getDescription()).contains("Invalid user ID"); } @Test void testNotFoundError() { GetUserRequest request = GetUserRequest.newBuilder() .setUserId("any-user") .build(); StatusRuntimeException exception = catchThrowableOfType( () -> blockingStub.getUser(request), StatusRuntimeException.class ); assertThat(exception.getStatus().getCode()).isEqualTo(Status.NOT_FOUND.getCode()); assertThat(exception.getStatus().getDescription()).contains("User not found"); } }

Test Configuration

# src/test/resources/application-test.yml grpc: server: port: 0 # Use random port for tests in-process-name: test-server logging: level: com.example.grpc: DEBUG io.grpc: WARN

Best Practices

  1. Test All RPC Types: Cover unary, client streaming, server streaming, and bidirectional streaming
  2. Error Scenarios: Test various error conditions and status codes
  3. Concurrent Testing: Test under concurrent load
  4. Mock Appropriately: Use mocks for external dependencies
  5. Integration Testing: Test with real servers when possible
  6. Performance Testing: Include performance benchmarks
// Example of comprehensive test annotations @GrpcTest @ExtendWith({MockitoExtension.class, GrpcCleanupExtension.class}) @ActiveProfiles("test") @Tag("grpc") @Tag("integration") class ComprehensiveGrpcTest { // Tests covering all scenarios }

Conclusion

gRPC testing in Java provides:

  • Comprehensive Coverage: Unit, integration, and performance testing
  • Multiple Testing Strategies: In-process, mock servers, TestContainers
  • Streaming Support: Full testing of streaming RPCs
  • Error Handling: Testing various gRPC status codes and error scenarios
  • Performance Validation: Load and concurrent testing capabilities

This comprehensive testing approach ensures your gRPC services are reliable, performant, and handle all edge cases correctly. The combination of in-process testing for fast unit tests and container-based testing for integration scenarios provides a robust testing strategy for gRPC-based microservices.

Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/

OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/

OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/

Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.

https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics

Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2

Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide

Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2

Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide

Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.

https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server

Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper