mvn.cmd : WARNING: A restricted method in java.lang.System has been called At line:5 char:1 + & $mvn -B test *> results-full.txt + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (WARNING: A rest...has been called:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file:/C:/Users/Ankur/ankurm-blog-tools/apache-maven-3.9.9/lib/jansi-2.4.1.jar) WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted methods will be blocked in a future release unless native access is enabled WARNING: A terminally deprecated method in sun.misc.Unsafe has been called WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/C:/Users/Ankur/ankurm-blog-tools/apache-maven-3.9.9/lib/guava-33.2.1-jre.jar) WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release [INFO] Scanning for projects... [INFO] [INFO] -----------------< com.ankurm.grpc:spring-grpc-boot4 >------------------ [INFO] Building Spring gRPC with Spring Boot 4 1.0.0 [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [WARNING] Parameter 'protocVersion' is unknown for plugin 'protobuf-maven-plugin:5.1.4:generate (generate)' [WARNING] Parameter 'binaryMavenPlugins' is unknown for plugin 'protobuf-maven-plugin:5.1.4:generate (generate)' [WARNING] Parameter 'protocVersion' is unknown for plugin 'protobuf-maven-plugin:5.1.4:generate (default)' [WARNING] Parameter 'binaryMavenPlugins' is unknown for plugin 'protobuf-maven-plugin:5.1.4:generate (default)' [INFO] [INFO] --- protobuf:5.1.4:generate (generate) @ spring-grpc-boot4 --- [INFO] Registering "C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\target\generated-sources\protobuf" as a Maven main source root for compiler plugins [INFO] Registering "C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\target\generated-sources\protobuf" as a Maven main source root for compiler plugins [INFO] Found 1 proto source file and 0 descriptor files to compile, but all are up-to-date so none will be built this time [INFO] NOTHING TO DO: There is nothing to do. If this is unexpected, review the above logs for more details. [INFO] [INFO] --- protobuf:5.1.4:generate (default) @ spring-grpc-boot4 --- [INFO] Registering "C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\target\generated-sources\protobuf" as a Maven main source root for compiler plugins [INFO] Registering "C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\target\generated-sources\protobuf" as a Maven main source root for compiler plugins [INFO] Found 1 proto source file and 0 descriptor files to compile, but all are up-to-date so none will be built this time [INFO] NOTHING TO DO: There is nothing to do. If this is unexpected, review the above logs for more details. [INFO] [INFO] --- resources:3.5.0:resources (default-resources) @ spring-grpc-boot4 --- [INFO] skip non existing resourceDirectory C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\src\main\resources [INFO] skip non existing resourceDirectory C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\src\main\resources [INFO] [INFO] --- compiler:3.15.0:compile (default-compile) @ spring-grpc-boot4 --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 20 source files with javac [debug parameters release 25] to target\classes [INFO] [INFO] --- resources:3.5.0:testResources (default-testResources) @ spring-grpc-boot4 --- [INFO] skip non existing resourceDirectory C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4\src\test\resources [INFO] [INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ spring-grpc-boot4 --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 5 source files with javac [debug parameters release 25] to target\test-classes [INFO] [INFO] --- surefire:3.5.6:test (default-test) @ spring-grpc-boot4 --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.ankurm.grpc._01_basics.FourCallTypesTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v4.1.0) 2026-07-31T23:03:43.299+05:30 INFO 36456 --- [ main] c.a.grpc._01_basics.FourCallTypesTest : Starting FourCallTypesTest using Java 25.0.3 with PID 36456 (started by Ankur in C:\Users\Ankur\ankurm-blog-tools\projects\spring-grpc-boot4) 2026-07-31T23:03:43.303+05:30 INFO 36456 --- [ main] c.a.grpc._01_basics.FourCallTypesTest : No active profile set, falling back to 1 default profile: "default" 2026-07-31T23:03:44.245+05:30 INFO 36456 --- [ main] o.s.grpc.server.NettyGrpcServerFactory : Registered gRPC service: com.ankurm.grpc.orders.OrderService 2026-07-31T23:03:44.245+05:30 INFO 36456 --- [ main] o.s.grpc.server.NettyGrpcServerFactory : Registered gRPC service: grpc.reflection.v1.ServerReflection 2026-07-31T23:03:44.245+05:30 INFO 36456 --- [ main] o.s.grpc.server.NettyGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health 2026-07-31T23:03:44.445+05:30 INFO 36456 --- [ main] o.s.g.s.lifecycle.GrpcServerLifecycle : gRPC Server started, listening on address: [/[0:0:0:0:0:0:0:0]:9090], port: 9090 2026-07-31T23:03:44.447+05:30 INFO 36456 --- [ main] o.s.g.server.InProcessGrpcServerFactory : Registered gRPC service: com.ankurm.grpc.orders.OrderService 2026-07-31T23:03:44.448+05:30 INFO 36456 --- [ main] o.s.g.server.InProcessGrpcServerFactory : Registered gRPC service: grpc.reflection.v1.ServerReflection 2026-07-31T23:03:44.448+05:30 INFO 36456 --- [ main] o.s.g.server.InProcessGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health 2026-07-31T23:03:44.449+05:30 INFO 36456 --- [ main] o.s.g.s.lifecycle.GrpcServerLifecycle : gRPC Server started, listening on address: [orders-test], port: -1 2026-07-31T23:03:44.451+05:30 INFO 36456 --- [ main] c.a.grpc._01_basics.FourCallTypesTest : Started FourCallTypesTest in 1.418 seconds (process running for 2.636) Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended WARNING: A Java agent has been loaded dynamically (C:\Users\Ankur\.m2\repository\net\bytebuddy\byte-buddy-agent\1.18.10\byte-buddy-agent-1.18.10.jar) WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information WARNING: Dynamic loading of agents will be disallowed by default in a future release ============================================================================== Client streaming: many requests, one response ============================================================================== accepted=3 totalCents=3000 >> requests.onCompleted() is the half-close. Without it the call hangs until >> the deadline -- and if you did not set a deadline, it hangs forever. ============================================================================== Unary: one request, one response ============================================================================== id=abc customer=ankur amountCents=1999 status=PENDING >> A @Service extending the generated ImplBase is registered automatically: >> it is a BindableService, and Boot 4 wires every such bean into the server. ============================================================================== Bidirectional streaming: many requests, many responses ============================================================================== sent 4, received 4, all status=PAID >> Request and response streams are INDEPENDENT. The server may respond >> before you finish sending, or not at all until you half-close. Do not >> assume a request/response pairing -- that is your protocol's job, not gRPC's. >> >> StreamObserver is NOT thread-safe. Calling onNext from two threads without >> synchronisation corrupts the stream, and the symptom is usually a >> deserialization error on the far side rather than anything pointing here. ============================================================================== Server streaming: one request, many responses ============================================================================== received 5 messages: [order-0, order-1, order-2, order-3, order-4] >> The blocking stub returns an Iterator. Each next() may block, and an >> exception surfaces mid-iteration -- so a try/catch around the loop body >> is not the same as one around the call. Wrap the whole iteration. [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.840 s -- in com.ankurm.grpc._01_basics.FourCallTypesTest [INFO] Running com.ankurm.grpc._02_troubleshooting.HardToDiagnoseTest ============================================================================== 4. Errors: UNKNOWN by default, useful only if you make them so ============================================================================== -- A handler that fails with a proper status and trailing metadata ----------- code : FAILED_PRECONDITION description : order o-42 is locked x-failure-reason : ORDER_LOCKED x-retry-after-seconds : 30 [PROBLEM] If the handler had simply thrown IllegalStateException, the caller would [PROBLEM] have received UNKNOWN with a null description. Nothing actionable at all. [FIX] Throw StatusRuntimeException with a code that means something: [FIX] NOT_FOUND / INVALID_ARGUMENT / FAILED_PRECONDITION -- do NOT retry [FIX] UNAVAILABLE / DEADLINE_EXCEEDED / RESOURCE_EXHAUSTED -- retry may help [FIX] Attach machine-readable context as trailers, not in the description string. >> The status code is the contract. Clients, retry policies, circuit breakers >> and dashboards all key off it, so choosing it carelessly makes every >> downstream behaviour wrong -- most damagingly, it makes non-retryable >> failures look retryable and turns one bad request into a storm. ============================================================================== 1. The 4 MB limit -- and why your integration tests will not catch it ============================================================================== -- Sending a 4 MB + 1 KB response over the IN-PROCESS transport -------------- received 4,195,328 bytes -- no error [PROBLEM] This message is OVER the 4 MB default limit and nothing complained. [PROBLEM] The in-process transport passes message objects BY REFERENCE -- it never [PROBLEM] serialises them -- so there are no bytes to measure and the limit cannot [PROBLEM] be applied. Over a real Netty transport the same call fails with: [PROBLEM] RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 4194304 >> This is a genuinely nasty testing trap. In-process transport is otherwise >> an excellent test transport -- interceptors, statuses, deadlines and >> cancellation all behave correctly -- but message size limits, compression >> and anything else that depends on the wire format do NOT apply. >> A payload-size regression will pass every in-process test you have. >> >> Test size limits against a real port, or not at all -- but do not believe >> a green in-process suite on this point. -- The fix in production, for when you do hit it ----------------------------- [FIX] The limit applies to the RECEIVER, and the two sides are configured separately: [FIX] large RESPONSE -> client inbound limit [FIX] spring.grpc.client.channel..inbound.message.max-size=16MB [FIX] large REQUEST -> server inbound limit [FIX] spring.grpc.server.inbound.message.max-size=16MB [FIX] Per call, without touching configuration: [FIX] stub.withMaxInboundMessageSize(16 * 1024 * 1024) [FIX] [FIX] Half of all 'I raised the limit and it still fails' reports are this [FIX] asymmetry: raising it on the server does nothing for a large RESPONSE. [FIX] [FIX] Do not raise it globally. A large limit turns a malformed request into an OOM. [FIX] Prefer streaming: the limit is per MESSAGE, so 1000 small messages are fine. ============================================================================== 3. Cancellation does not interrupt your thread -- you must check for it ============================================================================== -- Client starts a 200-message stream, then cancels after a few messages ----- received 3 messages, now cancelling 2026-07-31T23:03:45.543+05:30 INFO 36456 --- [ault-executor-0] c.ankurm.grpc.orders.OrderServiceImpl : client cancelled after 3 of 200 messages -- stopping work server noticed cancellation after 3 of 200 messages [FIX] The server loop checks Context.current().isCancelled() between messages and [FIX] returns. Without that check it would have produced all 200 messages into a [FIX] dead stream, doing every database read and every serialization for nothing. >> Any server handler that loops, or that does work in stages, should check >> Context.current().isCancelled(). For blocking work, propagate the Context >> to worker threads with Context.current().wrap(runnable) -- otherwise the >> cancellation flag is invisible to them. >> >> Also: after cancellation the stream is CLOSED. Calling onNext/onCompleted >> on it throws IllegalStateException. Just return. ============================================================================== 2. gRPC has NO default deadline -- a hung server hangs you forever ============================================================================== -- A call with an explicit deadline shorter than the server takes ------------ DEADLINE_EXCEEDED after 304 ms [PROBLEM] Without .withDeadlineAfter(...) that call would have blocked for the full [PROBLEM] 3 seconds -- and if the server never responded, forever. -- The fix ------------------------------------------------------------------- [FIX] Per call: stub.withDeadlineAfter(2, TimeUnit.SECONDS) [FIX] Per channel: register a DefaultDeadlineSetupClientInterceptor, or set [FIX] spring.grpc.client.channel..default.deadline=2s [FIX] Treat a missing deadline as a code-review failure, like a missing timeout [FIX] on an HTTP client. >> A deadline is ABSOLUTE and propagates: if service A calls B with 2s >> remaining, B sees 2s, not a fresh 2s. That is the property that stops a >> deep call chain from multiplying its timeouts -- and the reason you should >> set the deadline at the EDGE, not re-set it at every hop. [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.532 s -- in com.ankurm.grpc._02_troubleshooting.HardToDiagnoseTest [INFO] Running com.ankurm.grpc._03_exceptions.ExceptionMappingTest ============================================================================== Exception mapping: a domain exception becomes NOT_FOUND ============================================================================== code : NOT_FOUND description : no order with id missing-42 x-failure-reason : ORDER_NOT_FOUND [FIX] The service method just did: throw new OrderNotFoundException(id); [FIX] @GrpcAdvice + @GrpcExceptionHandler(OrderNotFoundException.class) did the rest. ============================================================================== Exception mapping: IllegalArgumentException becomes INVALID_ARGUMENT ============================================================================== code : INVALID_ARGUMENT description : order id must not be blank >> NOT_FOUND and INVALID_ARGUMENT are both in the 'never retry' family. >> A client that retries either is burning capacity on an answer that >> will not change. That distinction is the whole reason to map. ============================================================================== Exception mapping: the catch-all, and what it deliberately hides ============================================================================== code : INTERNAL description : internal error [PROBLEM] Without the advice this would have been UNKNOWN with a NULL description -- [PROBLEM] no code to branch on and no message to read. [FIX] With it, callers get a deliberate INTERNAL, and the sensitive text stays [FIX] server-side where it belongs. Log it with a correlation id and return that. >> Order matters: @GrpcExceptionHandler(Exception.class) is a catch-all, so >> more specific handlers must exist for the types you care about -- they are >> matched most-specific-first, exactly like @ExceptionHandler. [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 s -- in com.ankurm.grpc._03_exceptions.ExceptionMappingTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 10.913 s [INFO] Finished at: 2026-07-31T23:03:46+05:30 [INFO] ------------------------------------------------------------------------