This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new bfa42562b fix(grpc): fix rust/go grpc support (#3753)
bfa42562b is described below

commit bfa42562b295261c0cc4d178f20ccc0f67fdb809
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Jun 12 15:46:24 2026 +0800

    fix(grpc): fix rust/go grpc support (#3753)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    #3698
    #3738
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .github/workflows/ci.yml                           | 102 +++++++-
 ci/release.py                                      |   6 +
 compiler/fory_compiler/generators/go.py            |   4 -
 compiler/fory_compiler/generators/services/base.py |   5 +-
 compiler/fory_compiler/generators/services/go.py   |  61 +++--
 .../fory_compiler/tests/test_service_codegen.py    |  62 ++++-
 docs/compiler/compiler-guide.md                    |  58 ++---
 docs/compiler/flatbuffers-idl.md                   |  13 +-
 docs/compiler/generated-code.md                    |  86 +++++++
 docs/compiler/index.md                             |  15 +-
 docs/compiler/protobuf-idl.md                      |  27 ++-
 docs/compiler/schema-idl.md                        |  10 +-
 docs/guide/go/grpc-support.md                      | 241 +++++++++++++++++++
 docs/guide/go/index.md                             |   1 +
 docs/guide/go/troubleshooting.md                   |   2 +-
 docs/guide/java/grpc-support.md                    | 236 +++++++++++++++++++
 docs/guide/java/index.md                           |   1 +
 docs/guide/java/troubleshooting.md                 |   2 +-
 docs/guide/python/grpc-support.md                  | 210 +++++++++++++++++
 docs/guide/python/index.md                         |   1 +
 docs/guide/python/troubleshooting.md               |   2 +-
 docs/guide/rust/grpc-support.md                    | 229 ++++++++++++++++++
 docs/guide/rust/index.md                           |   1 +
 docs/guide/rust/troubleshooting.md                 |   2 +-
 integration_tests/grpc_tests/go/main.go            | 261 +++++++++++++++++----
 .../apache/fory/grpc_tests/GoGrpcInteropTest.java  |  43 ++++
 .../{GrpcInteropTest.java => GrpcTestBase.java}    | 207 +++++-----------
 .../fory/grpc_tests/PythonGrpcInteropTest.java     |  48 ++++
 .../fory/grpc_tests/RustGrpcInteropTest.java       |  44 ++++
 integration_tests/grpc_tests/run_tests.sh          |   3 +-
 30 files changed, 1685 insertions(+), 298 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6506f5152..6dc1891c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,6 +49,7 @@ jobs:
     name: Detect Changed Paths
     runs-on: ubuntu-latest
     outputs:
+      compiler: ${{ steps.filter.outputs.compiler }}
       cpp: ${{ steps.filter.outputs.cpp }}
       cpp_code: ${{ steps.filter.outputs.cpp_code }}
       java_code: ${{ steps.filter.outputs.java_code }}
@@ -68,6 +69,7 @@ jobs:
           BASE_REF: ${{ github.base_ref }}
         run: |
           if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then
+            echo "compiler=true" >> "$GITHUB_OUTPUT"
             echo "cpp=true" >> "$GITHUB_OUTPUT"
             echo "cpp_code=true" >> "$GITHUB_OUTPUT"
             echo "java_code=true" >> "$GITHUB_OUTPUT"
@@ -84,6 +86,12 @@ jobs:
           git fetch --no-tags --depth=1 origin 
"$BASE_REF:refs/remotes/origin/$BASE_REF"
           changed_files="$(git diff --name-only "origin/$BASE_REF" HEAD)"
 
+          if grep -Eq '^(compiler/)' <<< "$changed_files"; then
+            echo "compiler=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "compiler=false" >> "$GITHUB_OUTPUT"
+          fi
+
           if grep -Eq 
'^(cpp/|examples/cpp/|benchmarks/cpp/|integration_tests/idl_tests/cpp/|bazel/|BUILD$|WORKSPACE$|MODULE\.bazel$|\.bazelrc$)'
 <<< "$changed_files"; then
             echo "cpp=true" >> "$GITHUB_OUTPUT"
           else
@@ -687,8 +695,88 @@ jobs:
       - name: Run CI
         run: python ./ci/run_ci.py java --version integration_tests
 
-  grpc_tests:
-    name: Java/Python/Go/Rust gRPC Tests
+  grpc_java_python_tests:
+    name: Java/Python gRPC Tests
+    needs: changes
+    if: needs.changes.outputs.compiler == 'true'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v5
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: 21
+          distribution: "temurin"
+      - name: Set up Python 3.11
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+          cache: "pip"
+      - name: Cache Maven local repository
+        uses: actions/cache@v4
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Install Java artifacts for gRPC tests
+        run: |
+          cd java
+          mvn -T16 --no-transfer-progress clean install -DskipTests 
-Dmaven.javadoc.skip=true -Dmaven.source.skip=true
+      - name: Install Python gRPC dependencies
+        run: |
+          python -m pip install "grpcio>=1.62.2,<1.71"
+          python -m pip install -v -e python
+      - name: Generate gRPC test sources
+        run: python integration_tests/grpc_tests/generate_grpc.py
+      - name: Run Java/Python gRPC Tests
+        run: |
+          cd integration_tests/grpc_tests/java
+          mvn -T16 --no-transfer-progress -Dtest=PythonGrpcInteropTest test
+
+  grpc_java_go_tests:
+    name: Java/Go gRPC Tests
+    needs: changes
+    if: needs.changes.outputs.compiler == 'true'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v5
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: 21
+          distribution: "temurin"
+      - name: Set up Python 3.11
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+          cache: "pip"
+      - name: Cache Maven local repository
+        uses: actions/cache@v4
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Install Java artifacts for gRPC tests
+        run: |
+          cd java
+          mvn -T16 --no-transfer-progress clean install -DskipTests 
-Dmaven.javadoc.skip=true -Dmaven.source.skip=true
+      - name: Generate gRPC test sources
+        run: python integration_tests/grpc_tests/generate_grpc.py
+      - name: Build Go gRPC peer
+        run: |
+          cd integration_tests/grpc_tests/go
+          go build -o grpc-interop .
+      - name: Run Java/Go gRPC Tests
+        run: |
+          cd integration_tests/grpc_tests/java
+          mvn -T16 --no-transfer-progress -Dtest=GoGrpcInteropTest test
+
+  grpc_java_rust_tests:
+    name: Java/Rust gRPC Tests
+    needs: changes
+    if: needs.changes.outputs.compiler == 'true'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -718,8 +806,14 @@ jobs:
         run: |
           cd java
           mvn -T16 --no-transfer-progress clean install -DskipTests 
-Dmaven.javadoc.skip=true -Dmaven.source.skip=true
-      - name: Run Java/Python/Go/Rust gRPC Tests
-        run: ./integration_tests/grpc_tests/run_tests.sh
+      - name: Generate gRPC test sources
+        run: python integration_tests/grpc_tests/generate_grpc.py
+      - name: Build Rust gRPC peer
+        run: cargo build --manifest-path 
integration_tests/grpc_tests/rust/Cargo.toml --workspace --quiet
+      - name: Run Java/Rust gRPC Tests
+        run: |
+          cd integration_tests/grpc_tests/java
+          mvn -T16 --no-transfer-progress -Dtest=RustGrpcInteropTest test
 
   javascript:
     name: JavaScript CI
diff --git a/ci/release.py b/ci/release.py
index a6c810988..779d903a5 100644
--- a/ci/release.py
+++ b/ci/release.py
@@ -282,6 +282,12 @@ def bump_rust_version(new_version):
         rust_version,
         _update_cargo_package_version,
     )
+    _bump_version(
+        "integration_tests/grpc_tests/rust",
+        "Cargo.toml",
+        rust_version,
+        _update_rust_version,
+    )
 
 
 def bump_kotlin_version(new_version):
diff --git a/compiler/fory_compiler/generators/go.py 
b/compiler/fory_compiler/generators/go.py
index c4cc57e73..383dd8a6e 100644
--- a/compiler/fory_compiler/generators/go.py
+++ b/compiler/fory_compiler/generators/go.py
@@ -207,10 +207,6 @@ class GoGenerator(GoServiceGeneratorMixin, BaseGenerator):
         # Generate a single Go file with all types
         files.append(self.generate_file())
 
-        # Generate gRPC service stubs if requested
-        if self.options.grpc:
-            files.extend(self.generate_services())
-
         return files
 
     def get_package_name(self) -> str:
diff --git a/compiler/fory_compiler/generators/services/base.py 
b/compiler/fory_compiler/generators/services/base.py
index 2bc2bcfb2..402d5b8f8 100644
--- a/compiler/fory_compiler/generators/services/base.py
+++ b/compiler/fory_compiler/generators/services/base.py
@@ -18,7 +18,7 @@
 "Shared utilities for gRPC service stub generators."
 
 from enum import Enum
-from typing import List, Dict
+from typing import Dict
 from fory_compiler.ir.ast import RpcMethod
 
 
@@ -49,6 +49,3 @@ class ImportTracker:
 
     def add(self, alias: str, import_path: str) -> None:
         self._imports[alias] = import_path
-
-    def go_imports(self) -> List[str]:
-        return sorted(self._imports.values())
diff --git a/compiler/fory_compiler/generators/services/go.py 
b/compiler/fory_compiler/generators/services/go.py
index 399dbe685..264c24aed 100644
--- a/compiler/fory_compiler/generators/services/go.py
+++ b/compiler/fory_compiler/generators/services/go.py
@@ -17,7 +17,7 @@
 
 """Go gRPC service code generator."""
 
-from typing import List
+from typing import Dict, List
 from fory_compiler.generators.services.base import (
     ImportTracker,
     StreamingMode,
@@ -36,8 +36,22 @@ class GoServiceGeneratorMixin:
         ]
         if not local_services:
             return []
+        self._validate_method_name_collisions(local_services)
         return [self._generate_grpc_file(local_services)]
 
+    def _validate_method_name_collisions(self, services: List[Service]) -> 
None:
+        for service in services:
+            seen: Dict[str, str] = {}
+            for method in service.methods:
+                go_name = self.to_pascal_case(method.name)
+                prior = seen.get(go_name)
+                if prior is not None:
+                    raise ValueError(
+                        f"Go gRPC method name collision in service 
{service.name}: "
+                        f"{prior} and {method.name} both generate {go_name}"
+                    )
+                seen[go_name] = method.name
+
     def _generate_grpc_file(self, services: List[Service]) -> GeneratedFile:
         """Generate one _grpc.go file containing all services in the schema."""
         lines: List[str] = []
@@ -85,7 +99,6 @@ class GoServiceGeneratorMixin:
             '"google.golang.org/grpc/codes"',
             '"google.golang.org/grpc/mem"',
             '"google.golang.org/grpc/status"',
-            '"github.com/apache/fory/go/fory"',
         ]
 
         for alias, path in tracker._imports.items():
@@ -142,7 +155,6 @@ class GoServiceGeneratorMixin:
         lines: List[str] = []
         lines.append(f"type {self.to_camel_case(service.name)}Client struct 
{{")
         lines.append("\tcc grpc.ClientConnInterface")
-        lines.append("\tfory *fory.Fory")
         lines.append("}")
         lines.append("")
         return lines
@@ -150,11 +162,9 @@ class GoServiceGeneratorMixin:
     def _generate_new_client(self, service: Service) -> List[str]:
         lines: List[str] = []
         lines.append(
-            f"func New{service.name}Client(cc grpc.ClientConnInterface, f 
*fory.Fory) {service.name}Client {{"
-        )
-        lines.append(
-            f"\treturn &{self.to_camel_case(service.name)}Client{{cc: cc, 
fory: f}}"
+            f"func New{service.name}Client(cc grpc.ClientConnInterface) 
{service.name}Client {{"
         )
+        lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc: 
cc}}")
         lines.append("}")
         lines.append("")
         return lines
@@ -164,31 +174,24 @@ class GoServiceGeneratorMixin:
         lines.append(
             "// CodecV2 implements grpc/encoding.CodecV2 using Fory 
serialization."
         )
-        lines.append(
-            "// Pass a configured *fory.Fory instance with all message types 
registered."
-        )
-        lines.append("type CodecV2 struct {")
-        lines.append("\tFory *fory.Fory")
-        lines.append("}")
+        lines.append("// It uses the generated package-level thread-safe Fory 
runtime.")
+        lines.append("type CodecV2 struct{}")
         lines.append("")
         lines.append(
-            "// Marshal serializes v with Fory. The result is copied before 
being handed"
+            "// Marshal serializes v with Fory. The generated thread-safe 
runtime returns"
         )
         lines.append(
-            "// to gRPC because Fory reuses its internal write buffer across 
calls —"
+            "// a stable copy before releasing pooled Fory instances, so gRPC 
never sees"
         )
         lines.append(
-            "// streaming handlers may buffer multiple frames before sending, 
and without"
+            "// the reusable internal buffers owned by plain fory.Fory 
runtimes."
         )
-        lines.append("// a copy all frames would alias the last serialized 
value.")
         lines.append("func (c CodecV2) Marshal(v any) (mem.BufferSlice, error) 
{")
-        lines.append("\tb, err := c.Fory.Marshal(v)")
+        lines.append("\tb, err := getFory().Serialize(v)")
         lines.append("\tif err != nil {")
         lines.append("\t\treturn nil, err")
         lines.append("\t}")
-        lines.append("\tout := make([]byte, len(b))")
-        lines.append("\tcopy(out, b)")
-        lines.append("\treturn mem.BufferSlice{mem.NewBuffer(&out, nil)}, nil")
+        lines.append("\treturn mem.BufferSlice{mem.NewBuffer(&b, nil)}, nil")
         lines.append("}")
         lines.append("")
         lines.append(
@@ -204,7 +207,7 @@ class GoServiceGeneratorMixin:
         lines.append("\tfor _, buf := range data {")
         lines.append("\t\tn += copy(b[n:], buf.ReadOnlyData())")
         lines.append("\t}")
-        lines.append("\treturn c.Fory.Unmarshal(b, v)")
+        lines.append("\treturn getFory().Deserialize(b, v)")
         lines.append("}")
         lines.append("")
         lines.append(
@@ -229,7 +232,7 @@ class GoServiceGeneratorMixin:
                 )
                 lines.append(f"\tout := new({res_type[1:]})")
                 lines.append(
-                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{Fory: c.fory})}, opts...)"
+                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{})}, opts...)"
                 )
                 lines.append(
                     f'\terr := c.cc.Invoke(ctx, 
"{self.get_grpc_method_path(service, method)}", in, out, callOpts...)'
@@ -245,7 +248,7 @@ class GoServiceGeneratorMixin:
                     f"func (c *{self.to_camel_case(service.name)}Client) 
{self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts 
...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, 
error) {{"
                 )
                 lines.append(
-                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{Fory: c.fory})}, opts...)"
+                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{})}, opts...)"
                 )
                 lines.append(
                     f'\tstream, err := c.cc.NewStream(ctx, 
&_{service.name}_serviceDesc.Streams[{stream_index}], 
"{self.get_grpc_method_path(service, method)}", callOpts...)'
@@ -271,7 +274,7 @@ class GoServiceGeneratorMixin:
                     f"func (c *{self.to_camel_case(service.name)}Client) 
{self.to_pascal_case(method.name)}(ctx context.Context, opts 
...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, 
error) {{"
                 )
                 lines.append(
-                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{Fory: c.fory})}, opts...)"
+                    "\tcallOpts := 
append([]grpc.CallOption{grpc.ForceCodecV2(CodecV2{})}, opts...)"
                 )
                 lines.append(
                     f'\tstream, err := c.cc.NewStream(ctx, 
&_{service.name}_serviceDesc.Streams[{stream_index}], 
"{self.get_grpc_method_path(service, method)}", callOpts...)'
@@ -683,9 +686,7 @@ class GoServiceGeneratorMixin:
             mode = streaming_mode(method)
             if mode is StreamingMode.UNARY:
                 lines.append("\t\t{")
-                lines.append(
-                    f'\t\t\tMethodName:\t"{self.to_pascal_case(method.name)}",'
-                )
+                lines.append(f'\t\t\tMethodName:\t"{method.name}",')
                 lines.append(
                     
f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,"
                 )
@@ -700,9 +701,7 @@ class GoServiceGeneratorMixin:
                 continue
             else:
                 lines.append("\t\t{")
-                lines.append(
-                    f'\t\t\tStreamName:\t"{self.to_pascal_case(method.name)}",'
-                )
+                lines.append(f'\t\t\tStreamName:\t"{method.name}",')
                 lines.append(
                     
f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,"
                 )
diff --git a/compiler/fory_compiler/tests/test_service_codegen.py 
b/compiler/fory_compiler/tests/test_service_codegen.py
index 8170b9988..2fb7ad62b 100644
--- a/compiler/fory_compiler/tests/test_service_codegen.py
+++ b/compiler/fory_compiler/tests/test_service_codegen.py
@@ -237,19 +237,59 @@ def test_go_grpc_service_codegen():
     files = generate_service_files(schema, GoGenerator)
     assert len(files) == 1
     content = next(iter(files.values()))
-    assert "func NewGreeterClient(" in content
+    assert "func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient" 
in content
     assert "func RegisterGreeterServer(" in content
     assert "type GreeterClient interface" in content
     assert "type GreeterServer interface" in content
     assert "type UnimplementedGreeterServer struct" in content
-    assert "CodecV2{Fory: c.fory}" in content
-    assert "type CodecV2 struct" in content
+    assert "CodecV2{}" in content
+    assert "type CodecV2 struct{}" in content
     assert "func (c CodecV2) Marshal(v any) (mem.BufferSlice, error)" in 
content
     assert "func (c CodecV2) Unmarshal(data mem.BufferSlice, v any) error" in 
content
+    assert "getFory().Serialize(v)" in content
+    assert "getFory().Deserialize(b, v)" in content
+    assert "*fory.Fory" not in content
+    assert "c.fory" not in content
     assert '"/demo.greeter.Greeter/SayHello"' in content
     assert "mustEmbedUnimplementedGreeterServer()" in content
 
 
+def test_go_grpc_service_desc_uses_idl_method_names():
+    schema = parse_fdl(
+        dedent(
+            """
+            package demo.routes;
+
+            message Req {}
+            message Res {}
+
+            service Router {
+                rpc sayHello (Req) returns (Res);
+                rpc stream_replies (Req) returns (stream Res);
+                rpc clientTalk (stream Req) returns (Res);
+                rpc bidi_chat (stream Req) returns (stream Res);
+            }
+            """
+        )
+    )
+
+    content = next(iter(generate_service_files(schema, GoGenerator).values()))
+    assert "SayHello(ctx context.Context" in content
+    assert "StreamReplies(ctx context.Context" in content
+    assert "ClientTalk(ctx context.Context" in content
+    assert "BidiChat(ctx context.Context" in content
+    assert '"/demo.routes.Router/sayHello"' in content
+    assert '"/demo.routes.Router/stream_replies"' in content
+    assert '"/demo.routes.Router/clientTalk"' in content
+    assert '"/demo.routes.Router/bidi_chat"' in content
+    assert 'MethodName:\t"sayHello"' in content
+    assert 'StreamName:\t"stream_replies"' in content
+    assert 'StreamName:\t"clientTalk"' in content
+    assert 'StreamName:\t"bidi_chat"' in content
+    assert 'MethodName:\t"SayHello"' not in content
+    assert 'StreamName:\t"StreamReplies"' not in content
+
+
 def test_java_outer_classname_service_references_nested_model_types():
     schema = parse_fdl(
         dedent(
@@ -626,6 +666,17 @@ def test_grpc_method_name_collisions_fail():
     else:
         raise AssertionError("Expected Rust gRPC method name collision")
 
+    go_generator = GoGenerator(
+        schema, GeneratorOptions(output_dir=Path("/tmp"), grpc=True)
+    )
+    try:
+        go_generator.generate_services()
+    except ValueError as e:
+        assert "Go gRPC method name collision" in str(e)
+        assert "Foo and foo" in str(e)
+    else:
+        raise AssertionError("Expected Go gRPC method name collision")
+
 
 def test_java_python_grpc_method_keywords_are_safe_names():
     schema = parse_fdl(
@@ -778,18 +829,21 @@ def 
test_service_schema_produces_one_file_per_message_per_language():
         )
 
 
-def test_compile_service_schema_with_grpc_flag(tmp_path: Path):
+def test_compile_service_schema_with_grpc_flag(tmp_path: Path, capsys):
     example_path = Path(__file__).resolve().parents[2] / "examples" / 
"service.fdl"
     lang_dirs = {}
     for lang in ("java", "python", "rust", "go", "cpp", "csharp", "swift"):
         lang_dirs[lang] = tmp_path / lang
     ok = compile_file(example_path, lang_dirs, grpc=True, generated_outputs={})
+    output = capsys.readouterr().out
     assert ok is True
     for lang, lang_dir in lang_dirs.items():
         files = [p for p in lang_dir.rglob("*") if p.is_file()]
         assert len(files) >= 1, f"{lang}: expected at least one file with 
grpc=True"
     assert (lang_dirs["java"] / "demo" / "greeter" / 
"GreeterGrpc.java").exists()
     assert (lang_dirs["python"] / "demo_greeter_grpc.py").exists()
+    assert (lang_dirs["go"] / "demo_greeter_grpc.go").exists()
+    assert output.count("demo_greeter_grpc.go") == 1
     assert (lang_dirs["rust"] / "demo_greeter_service.rs").exists()
     assert (lang_dirs["rust"] / "demo_greeter_service_grpc.rs").exists()
 
diff --git a/docs/compiler/compiler-guide.md b/docs/compiler/compiler-guide.md
index 7204116f1..dbad7568f 100644
--- a/docs/compiler/compiler-guide.md
+++ b/docs/compiler/compiler-guide.md
@@ -52,27 +52,27 @@ foryc --scan-generated [OPTIONS]
 
 Compile options:
 
-| Option                                | Description                          
                 | Default       |
-| ------------------------------------- | 
----------------------------------------------------- | ------------- |
-| `--lang`                              | Comma-separated target languages     
                 | `all`         |
-| `--output`, `-o`                      | Output directory                     
                 | `./generated` |
-| `-I`, `--proto_path`, `--import_path` | Add directory to import search path 
(can be repeated) | (none)        |
-| `--java_out=DST_DIR`                  | Generate Java code in DST_DIR        
                 | (none)        |
-| `--python_out=DST_DIR`                | Generate Python code in DST_DIR      
                 | (none)        |
-| `--cpp_out=DST_DIR`                   | Generate C++ code in DST_DIR         
                 | (none)        |
-| `--go_out=DST_DIR`                    | Generate Go code in DST_DIR          
                 | (none)        |
-| `--rust_out=DST_DIR`                  | Generate Rust code in DST_DIR        
                 | (none)        |
-| `--csharp_out=DST_DIR`                | Generate C# code in DST_DIR          
                 | (none)        |
-| `--javascript_out=DST_DIR`            | Generate JavaScript/TypeScript code 
in DST_DIR        | (none)        |
-| `--swift_out=DST_DIR`                 | Generate Swift code in DST_DIR       
                 | (none)        |
-| `--dart_out=DST_DIR`                  | Generate Dart code in DST_DIR        
                 | (none)        |
-| `--scala_out=DST_DIR`                 | Generate Scala 3 code in DST_DIR     
                 | (none)        |
-| `--kotlin_out=DST_DIR`                | Generate Kotlin code in DST_DIR      
                 | (none)        |
-| `--go_nested_type_style`              | Go nested type naming: `camelcase` 
or `underscore`    | `underscore`  |
-| `--swift_namespace_style`             | Swift namespace style: `enum` or 
`flatten`            | `enum`        |
-| `--emit-fdl`                          | Emit translated FDL (for non-FDL 
inputs)              | `false`       |
-| `--emit-fdl-path`                     | Write translated FDL to this path 
(file or directory) | (stdout)      |
-| `--grpc`                              | Generate gRPC service companions for 
Java and Python  | `false`       |
+| Option                                | Description                          
                  | Default       |
+| ------------------------------------- | 
------------------------------------------------------ | ------------- |
+| `--lang`                              | Comma-separated target languages     
                  | `all`         |
+| `--output`, `-o`                      | Output directory                     
                  | `./generated` |
+| `-I`, `--proto_path`, `--import_path` | Add directory to import search path 
(can be repeated)  | (none)        |
+| `--java_out=DST_DIR`                  | Generate Java code in DST_DIR        
                  | (none)        |
+| `--python_out=DST_DIR`                | Generate Python code in DST_DIR      
                  | (none)        |
+| `--cpp_out=DST_DIR`                   | Generate C++ code in DST_DIR         
                  | (none)        |
+| `--go_out=DST_DIR`                    | Generate Go code in DST_DIR          
                  | (none)        |
+| `--rust_out=DST_DIR`                  | Generate Rust code in DST_DIR        
                  | (none)        |
+| `--csharp_out=DST_DIR`                | Generate C# code in DST_DIR          
                  | (none)        |
+| `--javascript_out=DST_DIR`            | Generate JavaScript/TypeScript code 
in DST_DIR         | (none)        |
+| `--swift_out=DST_DIR`                 | Generate Swift code in DST_DIR       
                  | (none)        |
+| `--dart_out=DST_DIR`                  | Generate Dart code in DST_DIR        
                  | (none)        |
+| `--scala_out=DST_DIR`                 | Generate Scala 3 code in DST_DIR     
                  | (none)        |
+| `--kotlin_out=DST_DIR`                | Generate Kotlin code in DST_DIR      
                  | (none)        |
+| `--go_nested_type_style`              | Go nested type naming: `camelcase` 
or `underscore`     | `underscore`  |
+| `--swift_namespace_style`             | Swift namespace style: `enum` or 
`flatten`             | `enum`        |
+| `--emit-fdl`                          | Emit translated FDL (for non-FDL 
inputs)               | `false`       |
+| `--emit-fdl-path`                     | Write translated FDL to this path 
(file or directory)  | (stdout)      |
+| `--grpc`                              | Generate gRPC service companions for 
supported outputs | `false`       |
 
 Schema-level file options are supported for language-specific generation 
choices.
 For `go_nested_type_style` and `swift_namespace_style`, the CLI flag overrides
@@ -141,23 +141,23 @@ foryc schema.fdl --output ./src/generated
 foryc user.fdl order.fdl product.fdl --output ./generated
 ```
 
-**Compile a simple schema containing service definitions (Java + Python 
models):**
+**Compile a simple schema containing service definitions (Java + Python + Rust 
models):**
 
 ```bash
-foryc compiler/examples/service.fdl --java_out=./generated/java 
--python_out=./generated/python
+foryc compiler/examples/service.fdl --java_out=./generated/java 
--python_out=./generated/python --rust_out=./generated/rust
 ```
 
-**Generate Java and Python gRPC service companions:**
+**Generate Java, Python, and Rust gRPC service companions:**
 
 ```bash
-foryc compiler/examples/service.fdl --java_out=./generated/java 
--python_out=./generated/python --grpc
+foryc compiler/examples/service.fdl --java_out=./generated/java 
--python_out=./generated/python --rust_out=./generated/rust --grpc
 ```
 
 The generated gRPC service code uses Fory to serialize request and response
-payloads. Java output imports grpc-java APIs and Python output imports `grpc`;
-applications that compile or run those generated service files must provide
-their own gRPC dependencies. Fory's Java and Python packages do not add a hard
-gRPC dependency for this feature.
+payloads. Java output imports grpc-java APIs, Python output imports `grpc`, and
+Rust output imports `tonic` and `bytes`; applications that compile or run those
+generated service files must provide their own gRPC dependencies. Fory packages
+do not add a hard gRPC dependency for this feature.
 
 **Use import search paths:**
 
diff --git a/docs/compiler/flatbuffers-idl.md b/docs/compiler/flatbuffers-idl.md
index 2744ba6ae..45e88e10a 100644
--- a/docs/compiler/flatbuffers-idl.md
+++ b/docs/compiler/flatbuffers-idl.md
@@ -125,8 +125,9 @@ message Container {
 ### Services
 
 FlatBuffers `rpc_service` definitions are translated to Fory services. With
-`--grpc`, the compiler emits Java and Python gRPC service companions that use
-Fory serialization for request and response payloads.
+`--grpc`, the compiler emits gRPC service companions for supported outputs such
+as Java, Python, Go, and Rust. These companions use Fory serialization for
+request and response payloads.
 
 ```fbs
 rpc_service SearchService {
@@ -136,12 +137,12 @@ rpc_service SearchService {
 ```
 
 ```bash
-foryc api.fbs --java_out=./generated/java --python_out=./generated/python 
--grpc
+foryc api.fbs --java_out=./generated/java --python_out=./generated/python 
--rust_out=./generated/rust --grpc
 ```
 
-Generated service code imports grpc APIs, so applications must provide 
grpc-java
-or `grpcio` dependencies when they compile or run those files. Fory packages do
-not add gRPC as a hard dependency.
+Generated service code imports grpc APIs, so applications must provide 
grpc-java,
+`grpcio`, grpc-go, or Rust `tonic` and `bytes` dependencies when they compile 
or
+run those files. Fory packages do not add gRPC as a hard dependency.
 
 ### Defaults and Metadata
 
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 567b9c57f..1764c47a1 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -404,6 +404,11 @@ Rust output is one module file per schema, for example:
 
 - `<rust_out>/addressbook.rs`
 
+When `--grpc` is used and the schema contains services, Rust also emits:
+
+- `<rust_out>/addressbook_service.rs`
+- `<rust_out>/addressbook_service_grpc.rs`
+
 ### Type Generation
 
 Unions map to Rust enums with `#[fory(id = ...)]` schema case attributes.
@@ -516,6 +521,51 @@ let bytes = person.to_bytes()?;
 let restored = Person::from_bytes(&bytes)?;
 ```
 
+### gRPC Service Companions
+
+When a schema contains services and the compiler is run with `--grpc`, Rust
+generation emits a service API module and a tonic binding module. For a schema
+module named `addressbook`, those files are `addressbook_service.rs` and
+`addressbook_service_grpc.rs`.
+
+The service API module contains the async trait and gRPC path constants:
+
+```rust
+#[::tonic::async_trait]
+pub trait AddressBookService: ::std::marker::Send + ::std::marker::Sync + 
'static {
+    async fn lookup(
+        &self,
+        request: ::tonic::Request<crate::addressbook::Person>,
+    ) -> ::std::result::Result<
+        ::tonic::Response<crate::addressbook::AddressBook>,
+        ::tonic::Status,
+    >;
+}
+
+pub const ADDRESS_BOOK_SERVICE_SERVICE_NAME: &str = 
"addressbook.AddressBookService";
+pub const ADDRESS_BOOK_SERVICE_LOOKUP_PATH: &str = 
"/addressbook.AddressBookService/Lookup";
+```
+
+The tonic binding module contains Fory-backed codecs, payload implementations,
+and client/server wrappers. It serializes each request or response with the
+generated model type's `to_bytes` and `from_bytes` helpers:
+
+```rust
+impl codec::ForyGrpcPayload for crate::addressbook::Person {
+    fn encode_fory_payload(&self) -> 
::std::result::Result<::std::vec::Vec<u8>, ::fory::Error> {
+        self.to_bytes()
+    }
+
+    fn decode_fory_payload(payload: &[u8]) -> ::std::result::Result<Self, 
::fory::Error> {
+        Self::from_bytes(payload)
+    }
+}
+```
+
+Applications compiling the generated Rust service files must provide `tonic` 
and
+`bytes` dependencies; Fory's Rust crate does not add those gRPC dependencies as
+hard dependencies.
+
 ## C++
 
 ### Output Layout
@@ -776,6 +826,42 @@ if err := restored.FromBytes(data); err != nil {
 }
 ```
 
+### gRPC Service Companions
+
+When a schema contains services and the compiler is run with `--grpc`, Go
+generation emits one `<module>_grpc.go` file next to the model file. The
+companion contains grpc-go client and server interfaces plus a Fory-backed
+`CodecV2`.
+
+```go
+type AddressBookServiceClient interface {
+    Lookup(ctx context.Context, in *Person, opts ...grpc.CallOption) 
(*AddressBook, error)
+}
+
+func NewAddressBookServiceClient(cc grpc.ClientConnInterface) 
AddressBookServiceClient { ... }
+
+type CodecV2 struct{}
+```
+
+The generated codec uses the same package-level thread-safe Fory runtime as the
+generated `ToBytes` and `FromBytes` helpers. Applications should pass
+`CodecV2{}` to grpc-go server options, and generated clients force the same
+codec on each call:
+
+```go
+server := grpc.NewServer(grpc.ForceServerCodecV2(addressbook.CodecV2{}))
+addressbook.RegisterAddressBookServiceServer(server, service)
+
+client := addressbook.NewAddressBookServiceClient(conn)
+```
+
+Go method names are exported as PascalCase identifiers, while the gRPC method
+path keeps the exact service and method names from the schema. Regenerate both
+peers after changing service or method names.
+
+Applications compiling these files must provide grpc-go dependencies; Fory Go
+packages do not add gRPC as a hard dependency.
+
 ## C\#
 
 ### Output Layout
diff --git a/docs/compiler/index.md b/docs/compiler/index.md
index be9990e01..0bcc5ba6f 100644
--- a/docs/compiler/index.md
+++ b/docs/compiler/index.md
@@ -23,9 +23,9 @@ Fory IDL is a schema definition language for Apache Fory that 
enables type-safe
 cross-language serialization. Define your data structures once and generate
 native data structure code for Java, Python, C++, Go, Rust,
 JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Fory IDL can also
-describe RPC services; for Java and Python, the compiler can generate gRPC
-service companions that use Fory serialization for request and response
-payloads.
+describe RPC services; for Java, Python, Go, and Rust, the compiler can
+generate gRPC service companions that use Fory serialization for request and
+response payloads.
 
 ## Example Schema
 
@@ -88,15 +88,16 @@ service AnimalService {
 }
 ```
 
-Generate Java and Python models plus gRPC service companions with:
+Generate Java, Python, and Rust models plus gRPC service companions with:
 
 ```bash
-foryc animals.fdl --java_out=./generated/java --python_out=./generated/python 
--grpc
+foryc animals.fdl --java_out=./generated/java --python_out=./generated/python 
--rust_out=./generated/rust --grpc
 ```
 
 The generated service code uses normal gRPC APIs, but request and response
-objects are serialized with Fory. Applications provide their own grpc-java or
-`grpcio` dependencies; Fory packages do not add gRPC as a hard dependency.
+objects are serialized with Fory. Applications provide their own grpc-java,
+`grpcio`, grpc-go, or Rust `tonic` and `bytes` dependencies; Fory packages do
+not add gRPC as a hard dependency.
 
 ## Why Fory IDL?
 
diff --git a/docs/compiler/protobuf-idl.md b/docs/compiler/protobuf-idl.md
index a683f8cd2..66e0e4bc1 100644
--- a/docs/compiler/protobuf-idl.md
+++ b/docs/compiler/protobuf-idl.md
@@ -49,13 +49,13 @@ how protobuf concepts map to Fory, and how to use 
protobuf-only Fory extension o
 | Circular refs      | Not supported                 | Supported               
              |
 | Unknown fields     | Preserved                     | Not preserved           
              |
 | Generated types    | Protobuf-specific model types | Native language 
constructs            |
-| gRPC ecosystem     | Native                        | Java/Python service 
codegen           |
+| gRPC ecosystem     | Native                        | Java/Python/Go/Rust 
service codegen   |
 
-Fory can generate Java and Python gRPC service companions with `--grpc`. Those
-services use normal gRPC transports but serialize request and response payloads
-with Fory rather than protobuf. For broad gRPC ecosystem tooling, schema
-reflection, and protobuf-native interceptors, protobuf remains the 
mature/default
-choice.
+Fory can generate Java, Python, Go, and Rust gRPC service companions with
+`--grpc`. Those services use normal gRPC transports but serialize request and
+response payloads with Fory rather than protobuf. For broad gRPC ecosystem
+tooling, schema reflection, and protobuf-native interceptors, protobuf remains
+the mature/default choice.
 
 ## Why Use Apache Fory
 
@@ -311,17 +311,18 @@ modifiers (and optional `ref(weak=true)` where needed).
 Replace protobuf generation steps with the Fory compiler invocation for target
 languages.
 
-For Java and Python services, add `--grpc` to emit gRPC companion code:
+For supported service outputs, add `--grpc` to emit gRPC companion code:
 
 ```bash
-foryc api.proto --java_out=./generated/java --python_out=./generated/python 
--grpc
+foryc api.proto --java_out=./generated/java --python_out=./generated/python 
--rust_out=./generated/rust --grpc
 ```
 
-Generated Java service files compile against grpc-java, and generated Python
-service modules import `grpc`. Add those dependencies in your application 
build;
-Fory packages do not add gRPC as a hard dependency. Protobuf `oneof` fields are
-translated to Fory union fields inside request and response messages.
-Direct union RPC request or response types are not part of normal protobuf RPC
+Generated Java service files compile against grpc-java, generated Python 
service
+modules import `grpc`, and generated Rust service files import `tonic` and
+`bytes`. Add those dependencies in your application build; Fory packages do not
+add gRPC as a hard dependency. Protobuf `oneof` fields are translated to Fory
+union fields inside request and response messages. Direct union RPC request or
+response types are not part of normal protobuf RPC
 syntax.
 
 ### Step 5: Run Compatibility Checks
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index c3d951b69..c59bbe4c6 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -907,7 +907,8 @@ union_field := ['repeated'] field_type IDENTIFIER '=' 
INTEGER [field_options] ';
 
 Services define RPC method contracts in Fory IDL. They are optional: schemas
 with services still generate the normal data model types, and gRPC service code
-is generated only when the compiler is run with `--grpc` for Java or Python.
+is generated only when the compiler is run with `--grpc` for supported language
+outputs such as Java, Python, Go, and Rust.
 
 ```protobuf
 message GetPetRequest [id=200] {
@@ -947,9 +948,10 @@ service PetDirectory {
 - Enum, primitive, collection, map, and array types are not valid direct RPC
   request or response types. Wrap those values in a message when they are part
   of a service contract.
-- The generated Java and Python gRPC companions use Fory serialization for each
-  RPC payload. Applications that compile or run those companions provide their
-  own grpc-java or `grpcio` dependency.
+- The generated gRPC companions use Fory serialization for each RPC payload.
+  Applications that compile or run those companions provide their own gRPC
+  dependency, such as grpc-java, `grpcio`, grpc-go, or Rust `tonic` and
+  `bytes`.
 
 **Grammar:**
 
diff --git a/docs/guide/go/grpc-support.md b/docs/guide/go/grpc-support.md
new file mode 100644
index 000000000..02418eba1
--- /dev/null
+++ b/docs/guide/go/grpc-support.md
@@ -0,0 +1,241 @@
+---
+title: gRPC Support
+sidebar_position: 13
+id: grpc_support
+license: |
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+---
+
+Fory can generate Go gRPC service companions for schemas that define services.
+The generated code uses grpc-go for transport and a Fory-backed `CodecV2` for
+request and response payloads.
+
+Use this mode when every RPC peer is generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload
+encoding. Use standard protobuf gRPC code generation when clients or tools must
+consume protobuf message bytes directly.
+
+## Add Dependencies
+
+Add grpc-go to your module. Fory Go packages do not add gRPC as a hard
+dependency.
+
+```bash
+go get google.golang.org/grpc
+```
+
+Your generated code also imports the Fory Go module:
+
+```bash
+go get github.com/apache/fory/go/fory
+```
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string reply = 1;
+}
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Go model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --go_out=./generated/go --grpc
+```
+
+For this schema, the Go generator emits:
+
+| File                           | Purpose                                     
 |
+| ------------------------------ | 
-------------------------------------------- |
+| `greeter/demo_greeter.go`      | Fory model types and registration helpers   
 |
+| `greeter/demo_greeter_grpc.go` | grpc-go client, server interfaces, and 
codec |
+
+Generated Go methods use exported PascalCase names such as `SayHello`. The
+underlying gRPC method path keeps the exact schema method name, so names such 
as
+`sayHello` or `say_hello` continue to route by their schema spelling.
+
+## Implement a Server
+
+Implement the generated `GreeterServer` interface, create a grpc-go server with
+the generated Fory codec, and register the service.
+
+```go
+package main
+
+import (
+    "context"
+    "log"
+    "net"
+
+    "google.golang.org/grpc"
+
+    "example.com/app/generated/go/greeter"
+)
+
+type greeterService struct {
+    greeter.UnimplementedGreeterServer
+}
+
+func (greeterService) SayHello(
+    ctx context.Context,
+    request *greeter.HelloRequest,
+) (*greeter.HelloReply, error) {
+    return &greeter.HelloReply{Reply: "Hello, " + request.Name}, nil
+}
+
+func main() {
+    listener, err := net.Listen("tcp", ":50051")
+    if err != nil {
+        log.Fatal(err)
+    }
+
+    server := grpc.NewServer(
+        grpc.ForceServerCodecV2(greeter.CodecV2{}),
+    )
+    greeter.RegisterGreeterServer(server, greeterService{})
+
+    if err := server.Serve(listener); err != nil {
+        log.Fatal(err)
+    }
+}
+```
+
+`grpc.ForceServerCodecV2(...)` is required so the server decodes incoming 
frames
+with the generated Fory codec instead of the default protobuf codec.
+
+Use the zero-value generated `CodecV2{}` for the service schema. The generated
+client methods force the same codec for outgoing calls.
+
+## Create a Client
+
+The generated client constructor accepts a grpc-go connection. Generated client
+methods force the generated Fory codec for each call.
+
+```go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+    "time"
+
+    "google.golang.org/grpc"
+    "google.golang.org/grpc/credentials/insecure"
+
+    "example.com/app/generated/go/greeter"
+)
+
+func main() {
+    conn, err := grpc.NewClient(
+        "localhost:50051",
+        grpc.WithTransportCredentials(insecure.NewCredentials()),
+    )
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer conn.Close()
+
+    client := greeter.NewGreeterClient(conn)
+
+    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+    defer cancel()
+
+    reply, err := client.SayHello(ctx, &greeter.HelloRequest{Name: "Fory"})
+    if err != nil {
+        log.Fatal(err)
+    }
+    fmt.Println(reply.Reply)
+}
+```
+
+## Streaming RPCs
+
+Fory service definitions can use unary, server-streaming, client-streaming, and
+bidirectional streaming RPC shapes:
+
+```protobuf
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+  rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+  rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+  rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Generated Go code follows grpc-go conventions:
+
+- Unary methods take `context.Context`, a request pointer, and return a 
response
+  pointer plus `error`.
+- Server-streaming client methods return a generated stream client.
+- Client-streaming server methods receive a generated stream server.
+- Bidirectional streaming methods use generated stream client and server
+  interfaces.
+- The generated codec is used for every message frame, including streaming
+  frames.
+
+## Operations
+
+The generated service companion only supplies Fory serialization. Operational
+behavior remains standard grpc-go behavior:
+
+- Deadlines and cancellations
+- TLS and credentials
+- Unary and stream interceptors
+- Status codes and metadata
+- Name resolution and load balancing
+- Connection lifecycle and backoff
+
+## Troubleshooting
+
+### Missing `google.golang.org/grpc` Packages
+
+Add grpc-go to your module:
+
+```bash
+go get google.golang.org/grpc
+```
+
+### `grpc: error while marshaling`
+
+Confirm that both the client and server use the generated `CodecV2{}` and that
+the generated model file is compiled into the same package as the gRPC 
companion.
+
+### `UNIMPLEMENTED`
+
+Confirm that the generated service was registered with
+`RegisterGreeterServer(...)`, and that the client and server were generated 
from
+the same package, service, and method names.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/docs/guide/go/index.md b/docs/guide/go/index.md
index 52f162fcd..2cf4ebaf4 100644
--- a/docs/guide/go/index.md
+++ b/docs/guide/go/index.md
@@ -150,6 +150,7 @@ See [Xlang Serialization](xlang-serialization.md) for type 
mapping and compatibi
 | [Schema Evolution](schema-evolution.md)         | Forward/backward 
compatibility         |
 | [Custom Serializers](custom-serializers.md)     | Extend serialization 
behavior          |
 | [Thread Safety](thread-safety.md)               | Concurrent usage patterns  
            |
+| [gRPC Support](grpc-support.md)                 | Fory payloads over grpc-go 
            |
 | [Troubleshooting](troubleshooting.md)           | Common issues and 
solutions            |
 
 ## Related Resources
diff --git a/docs/guide/go/troubleshooting.md b/docs/guide/go/troubleshooting.md
index 7bcfb2d9f..2c538385e 100644
--- a/docs/guide/go/troubleshooting.md
+++ b/docs/guide/go/troubleshooting.md
@@ -1,6 +1,6 @@
 ---
 title: Troubleshooting
-sidebar_position: 13
+sidebar_position: 14
 id: troubleshooting
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/java/grpc-support.md b/docs/guide/java/grpc-support.md
new file mode 100644
index 000000000..bfa1781e3
--- /dev/null
+++ b/docs/guide/java/grpc-support.md
@@ -0,0 +1,236 @@
+---
+title: gRPC Support
+sidebar_position: 17
+id: grpc_support
+license: |
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+---
+
+Fory can generate Java gRPC service companions for schemas that define
+services. The generated service code uses normal grpc-java channels, servers,
+deadlines, status codes, interceptors, and transport security, while request
+and response objects are serialized with Fory instead of protobuf.
+
+Use this mode when both sides of the RPC are generated from the same Fory IDL,
+protobuf IDL, or FlatBuffers IDL and you want gRPC transport semantics with
+Fory payload encoding. Use standard protobuf gRPC code generation when your API
+must be consumed by generic protobuf clients, reflection tools, or components
+that expect protobuf message bytes.
+
+## Add Dependencies
+
+The generated Java service files compile against grpc-java. Fory Java artifacts
+do not add gRPC as a hard dependency, so add grpc-java dependencies in your
+application build and align the version with the rest of your service stack.
+
+Maven:
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>org.apache.fory</groupId>
+    <artifactId>fory-core</artifactId>
+    <version>${fory.version}</version>
+  </dependency>
+  <dependency>
+    <groupId>io.grpc</groupId>
+    <artifactId>grpc-api</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-netty-shaded</artifactId>
+    <version>${grpc.version}</version>
+  </dependency>
+</dependencies>
+```
+
+Gradle:
+
+```kotlin
+dependencies {
+  implementation("org.apache.fory:fory-core:$foryVersion")
+  implementation("io.grpc:grpc-api:$grpcVersion")
+  implementation("io.grpc:grpc-stub:$grpcVersion")
+  implementation("io.grpc:grpc-netty-shaded:$grpcVersion")
+}
+```
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string reply = 1;
+}
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Java model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --java_out=./generated/java --grpc
+```
+
+For this schema, the Java generator emits:
+
+| File                     | Purpose                                      |
+| ------------------------ | -------------------------------------------- |
+| `HelloRequest.java`      | Fory model type for the request              |
+| `HelloReply.java`        | Fory model type for the response             |
+| `GreeterForyModule.java` | Fory registration module for generated types |
+| `GreeterGrpc.java`       | grpc-java service base class, stubs, codecs  |
+
+## Implement a Server
+
+Extend the generated `GreeterGrpc.GreeterImplBase` class and register it with a
+standard grpc-java `Server`.
+
+```java
+package demo.greeter;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+
+final class GreeterService extends GreeterGrpc.GreeterImplBase {
+  @Override
+  public void sayHello(
+      HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+    HelloReply reply = new HelloReply();
+    reply.setReply("Hello, " + request.getName());
+    responseObserver.onNext(reply);
+    responseObserver.onCompleted();
+  }
+}
+
+public final class GreeterServer {
+  public static void main(String[] args) throws Exception {
+    Server server =
+        ServerBuilder.forPort(50051)
+            .addService(new GreeterService())
+            .build()
+            .start();
+    server.awaitTermination();
+  }
+}
+```
+
+Generated request and response types are registered by the generated code, so
+service implementations do not perform manual serializer registration.
+
+## Create a Client
+
+Use the generated stubs with an ordinary grpc-java channel:
+
+```java
+package demo.greeter;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+
+public final class GreeterClient {
+  public static void main(String[] args) {
+    ManagedChannel channel =
+        ManagedChannelBuilder.forAddress("localhost", 50051)
+            .usePlaintext()
+            .build();
+    try {
+      GreeterGrpc.GreeterBlockingStub stub =
+          GreeterGrpc.newBlockingStub(channel);
+
+      HelloRequest request = new HelloRequest();
+      request.setName("Fory");
+      HelloReply reply = stub.sayHello(request);
+      System.out.println(reply.getReply());
+    } finally {
+      channel.shutdownNow();
+    }
+  }
+}
+```
+
+For asynchronous calls, use `GreeterGrpc.newStub(channel)`. For future-based
+unary calls, use `GreeterGrpc.newFutureStub(channel)`.
+
+## Streaming RPCs
+
+Fory service definitions can use the same gRPC streaming shapes:
+
+```protobuf
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+  rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+  rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+  rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Generated Java service methods follow grpc-java conventions:
+
+- Unary and server-streaming methods receive a request object and a
+  `StreamObserver` for responses.
+- Client-streaming and bidirectional methods return a `StreamObserver` for
+  incoming requests and receive a `StreamObserver` for outgoing responses.
+- Blocking stubs expose the grpc-java blocking APIs for supported streaming
+  shapes.
+
+## Operations
+
+The generated service code only replaces request and response serialization.
+All normal gRPC operational features still belong to grpc-java:
+
+- Deadlines and cancellations
+- TLS and authentication
+- Name resolution and load balancing
+- Client and server interceptors
+- Status codes and metadata
+- Channel pooling and lifecycle management
+
+## Troubleshooting
+
+### Missing `io.grpc` or Guava Classes
+
+Add the grpc-java dependencies shown above. Generated Fory service files import
+grpc-java APIs, but Fory Java artifacts intentionally do not depend on gRPC.
+
+### `UNIMPLEMENTED`
+
+Confirm that the generated service implementation is registered with
+`ServerBuilder.addService(...)`, and that the client and server were generated
+from the same package, service, and method names.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/docs/guide/java/index.md b/docs/guide/java/index.md
index ac8ed7f46..580701f47 100644
--- a/docs/guide/java/index.md
+++ b/docs/guide/java/index.md
@@ -244,6 +244,7 @@ ThreadSafeFory threadLocalFory = Fory.builder()
 - [Object Copy](object-copy.md) - Deep-copy Java object graphs in memory
 - [Compression](compression.md) - Integer, long, and array compression options
 - [Virtual Threads](virtual-threads.md) - Virtual-thread usage and pool sizing 
guidance
+- [gRPC Support](grpc-support.md) - Fory payloads over grpc-java
 - [Type Registration](type-registration.md) - Class registration and security
 - [Custom Serializers](custom-serializers.md) - Implement custom serializers
 - [Xlang Serialization](xlang-serialization.md) - Serialize data for other 
languages
diff --git a/docs/guide/java/troubleshooting.md 
b/docs/guide/java/troubleshooting.md
index 141f645b9..2795f8376 100644
--- a/docs/guide/java/troubleshooting.md
+++ b/docs/guide/java/troubleshooting.md
@@ -1,6 +1,6 @@
 ---
 title: Troubleshooting
-sidebar_position: 17
+sidebar_position: 18
 id: troubleshooting
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/python/grpc-support.md 
b/docs/guide/python/grpc-support.md
new file mode 100644
index 000000000..c5106f91b
--- /dev/null
+++ b/docs/guide/python/grpc-support.md
@@ -0,0 +1,210 @@
+---
+title: gRPC Support
+sidebar_position: 13
+id: grpc_support
+license: |
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+---
+
+Fory can generate Python gRPC service companions for schemas that define
+services. The generated modules use `grpcio` for transport and use Fory to
+serialize request and response objects.
+
+Use this mode when every RPC peer is generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload
+encoding. Use standard protobuf gRPC code generation when clients or tools must
+consume protobuf message bytes directly.
+
+## Install Dependencies
+
+Install `grpcio` alongside `pyfory`. The generated companion imports `grpc`, 
but
+`pyfory` does not add gRPC as a hard dependency.
+
+```bash
+pip install pyfory grpcio
+```
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string reply = 1;
+}
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Python model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --python_out=./generated/python --grpc
+```
+
+For this schema, the Python generator emits:
+
+| File                   | Purpose                                     |
+| ---------------------- | ------------------------------------------- |
+| `demo_greeter.py`      | Fory dataclasses and registration helpers   |
+| `demo_greeter_grpc.py` | `grpcio` stub, servicer base, and registrar |
+
+The module name is derived from the Fory package by replacing dots with
+underscores. A schema with no package uses `generated.py` and
+`generated_grpc.py`.
+
+## Implement a Server
+
+Subclass the generated servicer and register it with a normal `grpcio` server.
+Generated Python method names use snake_case, while the gRPC wire path keeps 
the
+original IDL method name.
+
+```python
+from concurrent import futures
+
+import grpc
+
+import demo_greeter
+import demo_greeter_grpc
+
+
+class Greeter(demo_greeter_grpc.GreeterServicer):
+    def say_hello(self, request, context):
+        return demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+
+
+def serve():
+    server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
+    demo_greeter_grpc.add_servicer(Greeter(), server)
+    server.add_insecure_port("[::]:50051")
+    server.start()
+    server.wait_for_termination()
+
+
+if __name__ == "__main__":
+    serve()
+```
+
+Generated request and response types are serialized by the generated companion,
+so service implementations do not perform manual Fory registration.
+
+## Create a Client
+
+Use the generated stub with a normal `grpcio` channel:
+
+```python
+import grpc
+
+import demo_greeter
+import demo_greeter_grpc
+
+
+def main():
+    with grpc.insecure_channel("localhost:50051") as channel:
+        stub = demo_greeter_grpc.GreeterStub(channel)
+        reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
+        print(reply.reply)
+
+
+if __name__ == "__main__":
+    main()
+```
+
+`grpcio` still owns channel options, credentials, deadlines, metadata, retries,
+and interceptors.
+
+## Streaming RPCs
+
+Fory service definitions can use unary, server-streaming, client-streaming, and
+bidirectional streaming RPC shapes:
+
+```protobuf
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+  rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+  rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+  rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Generated Python code follows `grpcio` conventions:
+
+- Unary stubs call `channel.unary_unary(...)`.
+- Server-streaming stubs return an iterator over response objects.
+- Client-streaming stubs accept an iterator of request objects.
+- Bidirectional stubs accept a request iterator and return a response iterator.
+- Servicer methods use snake_case names and return either a single response or
+  an iterator, depending on the streaming shape.
+
+Example server-streaming implementation:
+
+```python
+class Greeter(demo_greeter_grpc.GreeterServicer):
+    def lots_of_replies(self, request, context):
+        for index in range(3):
+            yield demo_greeter.HelloReply(
+                reply=f"Hello {request.name}, response {index}"
+            )
+```
+
+## Operations
+
+The generated service companion only supplies Fory serialization callbacks.
+Operational behavior remains standard `grpcio` behavior:
+
+- Deadlines and cancellations
+- TLS and authentication credentials
+- Client and server interceptors
+- Status codes, details, and metadata
+- Channel and server lifecycle
+- Thread pool sizing for synchronous servers
+
+## Troubleshooting
+
+### `ModuleNotFoundError: No module named 'grpc'`
+
+Install `grpcio` in the environment that runs the generated service module:
+
+```bash
+pip install grpcio
+```
+
+### `TypeError: Unsupported gRPC servicer type`
+
+Pass an instance of the generated servicer subclass to
+`demo_greeter_grpc.add_servicer(...)`. If the schema contains multiple 
services,
+the generated registrar accepts only the matching generated servicer types.
+
+### `UNIMPLEMENTED`
+
+Confirm that the generated servicer was registered with the server, and that 
the
+client and server were generated from the same package, service, and method
+names.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/docs/guide/python/index.md b/docs/guide/python/index.md
index 36c0cb9ba..5b74d7de6 100644
--- a/docs/guide/python/index.md
+++ b/docs/guide/python/index.md
@@ -159,6 +159,7 @@ See [Native Serialization](native-serialization.md) for 
Python-only serializatio
 - [Type Registration](type-registration.md) - User-defined type registration
 - [Custom Serializers](custom-serializers.md) - Extend serialization behavior
 - [Row Format](row-format.md) - Zero-copy row format
+- [gRPC Support](grpc-support.md) - Fory payloads over grpcio
 
 ## Links
 
diff --git a/docs/guide/python/troubleshooting.md 
b/docs/guide/python/troubleshooting.md
index bd4124d49..8dc56c160 100644
--- a/docs/guide/python/troubleshooting.md
+++ b/docs/guide/python/troubleshooting.md
@@ -1,6 +1,6 @@
 ---
 title: Troubleshooting
-sidebar_position: 13
+sidebar_position: 14
 id: troubleshooting
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/rust/grpc-support.md b/docs/guide/rust/grpc-support.md
new file mode 100644
index 000000000..3cb6f3011
--- /dev/null
+++ b/docs/guide/rust/grpc-support.md
@@ -0,0 +1,229 @@
+---
+title: gRPC Support
+sidebar_position: 12
+id: grpc_support
+license: |
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+---
+
+Fory can generate Rust gRPC service companions for schemas that define
+services. The generated code uses `tonic` for transport and Fory for request 
and
+response payload serialization.
+
+Use this mode when every RPC peer is generated from the same Fory IDL, protobuf
+IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload
+encoding. Use standard protobuf gRPC code generation when clients or tools must
+consume protobuf message bytes directly.
+
+## Add Dependencies
+
+Add `tonic` and `bytes` to the crate that compiles the generated service files.
+Fory Rust crates do not add gRPC as a hard dependency. Add `tokio` for async
+servers and clients, and `tokio-stream` when your service implementation needs
+to build streaming responses.
+
+```toml
+[dependencies]
+fory = "1.1.0"
+bytes = "1"
+tonic = { version = "0.14", features = ["transport"] }
+tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
+tokio-stream = "0.1"
+```
+
+Use dependency versions that are compatible with the rest of your service
+stack.
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string reply = 1;
+}
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Rust model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --rust_out=./generated/rust --grpc
+```
+
+For this schema, the Rust generator emits:
+
+| File                           | Purpose                                     
 |
+| ------------------------------ | 
-------------------------------------------- |
+| `demo_greeter.rs`              | Fory model types and registration helpers   
 |
+| `demo_greeter_service.rs`      | Async service trait and gRPC path constants 
 |
+| `demo_greeter_service_grpc.rs` | tonic client, server wrapper, and Fory 
codec |
+
+Add the generated files to your crate root:
+
+```rust
+pub mod demo_greeter;
+pub mod demo_greeter_service;
+pub mod demo_greeter_service_grpc;
+```
+
+## Implement a Server
+
+Implement the generated async trait and add the generated server wrapper to a
+normal `tonic` server.
+
+```rust
+use demo_greeter::{HelloReply, HelloRequest};
+use demo_greeter_service::Greeter;
+use demo_greeter_service_grpc::greeter_server::GreeterServer;
+use tonic::{Request, Response, Status};
+
+#[derive(Default)]
+struct MyGreeter;
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+    async fn say_hello(
+        &self,
+        request: Request<HelloRequest>,
+    ) -> Result<Response<HelloReply>, Status> {
+        let request = request.into_inner();
+        Ok(Response::new(HelloReply {
+            reply: format!("Hello, {}", request.name),
+        }))
+    }
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let addr = "[::1]:50051".parse()?;
+    tonic::transport::Server::builder()
+        .add_service(GreeterServer::new(MyGreeter::default()))
+        .serve(addr)
+        .await?;
+    Ok(())
+}
+```
+
+Generated request and response types are serialized by the generated service
+code, so service implementations do not perform manual Fory registration.
+
+## Create a Client
+
+Use the generated tonic client:
+
+```rust
+use demo_greeter::HelloRequest;
+use demo_greeter_service_grpc::greeter_client::GreeterClient;
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut client = GreeterClient::connect("http://[::1]:50051";).await?;
+    let response = client
+        .say_hello(HelloRequest {
+            name: "Fory".to_string(),
+        })
+        .await?;
+    println!("{}", response.into_inner().reply);
+    Ok(())
+}
+```
+
+`tonic` still owns channel configuration, TLS, deadlines, metadata,
+interceptors, and transport lifecycle.
+
+## Streaming RPCs
+
+Fory service definitions can use unary, server-streaming, client-streaming, and
+bidirectional streaming RPC shapes:
+
+```protobuf
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+  rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+  rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+  rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
+
+Generated Rust code follows tonic conventions:
+
+- Unary methods use `tonic::Request<T>` and return `tonic::Response<U>`.
+- Server-streaming methods return a response whose inner value is a stream of
+  `Result<U, tonic::Status>`.
+- Client-streaming and bidirectional methods receive `tonic::Streaming<T>`.
+- The generated client module exposes matching async methods for each service
+  method.
+- The generated codec is used for every message frame, including streaming
+  frames.
+
+Use the generated trait signatures as the source of truth for the concrete
+associated stream types in your service implementation.
+
+## Thread Safety and Payload Types
+
+Generated Rust gRPC payloads must be `Send + 'static` so tonic can move request
+and response values across async tasks. If a schema uses non-thread-safe
+reference metadata for a request or response type, Rust gRPC generation rejects
+that service. Use thread-safe reference shapes for gRPC payloads, or keep the
+non-thread-safe type out of the RPC boundary.
+
+## Operations
+
+The generated service companion only supplies Fory serialization and tonic
+bindings. Operational behavior remains standard tonic behavior:
+
+- Deadlines and cancellations
+- TLS and authentication
+- Tower middleware and interceptors
+- Status codes and metadata
+- Channel and server lifecycle
+- Backpressure through async streams
+
+## Troubleshooting
+
+### Missing `tonic` or `bytes` Crates
+
+Add the dependencies shown above to the crate that compiles the generated
+service files.
+
+### `UNIMPLEMENTED`
+
+Confirm that the generated server wrapper was added with
+`Server::builder().add_service(...)`, and that the client and server were
+generated from the same package, service, and method names.
+
+### Non-Thread-Safe Reference Errors During Code Generation
+
+Rust gRPC payloads must be `Send + 'static`. Change the request or response
+schema to use thread-safe reference shapes, or wrap the non-thread-safe data 
in a
+type that is not part of the gRPC payload.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/docs/guide/rust/index.md b/docs/guide/rust/index.md
index 93c3537ed..c2212a1f7 100644
--- a/docs/guide/rust/index.md
+++ b/docs/guide/rust/index.md
@@ -193,3 +193,4 @@ fory-derive/           # Procedural macros
 - [Polymorphism](polymorphism.md) - Trait object serialization
 - [Custom Serializers](custom-serializers.md) - Extend serialization behavior
 - [Row Format](row-format.md) - Zero-copy row-based format
+- [gRPC Support](grpc-support.md) - Fory payloads over tonic
diff --git a/docs/guide/rust/troubleshooting.md 
b/docs/guide/rust/troubleshooting.md
index 6b46d1b6d..285c99a8d 100644
--- a/docs/guide/rust/troubleshooting.md
+++ b/docs/guide/rust/troubleshooting.md
@@ -1,6 +1,6 @@
 ---
 title: Troubleshooting
-sidebar_position: 12
+sidebar_position: 13
 id: troubleshooting
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/integration_tests/grpc_tests/go/main.go 
b/integration_tests/grpc_tests/go/main.go
index caa9e3909..0d414d2b5 100644
--- a/integration_tests/grpc_tests/go/main.go
+++ b/integration_tests/grpc_tests/go/main.go
@@ -16,7 +16,7 @@
 // under the License.
 
 // Binary grpc-interop is the Go peer for Java-driven gRPC integration tests.
-// It is invoked as a subprocess by GrpcInteropTest.java and supports two 
modes:
+// It is invoked as a subprocess by Java gRPC integration tests and supports 
two modes:
 //
 //     server --port-file <path>  start a gRPC server and write the bound port 
to the file
 //     client --target <addr>     connect to addr and exercise all four 
streaming modes
@@ -32,23 +32,13 @@ import (
        "os"
        "strings"
 
-       "github.com/apache/fory/go/fory"
        grpc_fdl 
"github.com/apache/fory/integration_tests/grpc_tests/go/generated/grpc_fdl"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
-       "google.golang.org/grpc/encoding"
 )
 
 // --- helpers ----------------------------------------------------------------
 
-func newFory() *fory.Fory {
-       f := fory.New(fory.WithXlang(true), fory.WithRefTracking(true), 
fory.WithCompatible(true))
-       if err := grpc_fdl.RegisterTypes(f); err != nil {
-               log.Fatalf("RegisterTypes: %v", err)
-       }
-       return f
-}
-
 func fdlResponse(req *grpc_fdl.GrpcFdlRequest, tag string, offset int) 
*grpc_fdl.GrpcFdlResponse {
        return &grpc_fdl.GrpcFdlResponse{
                Id:      fmt.Sprintf("%s:%s", tag, req.Id),
@@ -73,25 +63,59 @@ func fdlAggregate(requests []*grpc_fdl.GrpcFdlRequest) 
*grpc_fdl.GrpcFdlResponse
        }
 }
 
-// protoFallbackCodec overrides the built-in "proto" codec (v1 registry) with
-// Fory so the server can decode requests from Java clients, which send with 
the
-// default content-type (application/grpc) rather than application/grpc+fory.
-type protoFallbackCodec struct{ grpc_fdl.CodecV2 }
+func fdlRequestUnion(req *grpc_fdl.GrpcFdlRequest) *grpc_fdl.GrpcFdlUnion {
+       union := grpc_fdl.RequestGrpcFdlUnion(req)
+       return &union
+}
 
-func (protoFallbackCodec) Name() string { return "proto" }
+func fdlResponseUnion(response *grpc_fdl.GrpcFdlResponse) 
*grpc_fdl.GrpcFdlUnion {
+       union := grpc_fdl.ResponseGrpcFdlUnion(response)
+       return &union
+}
 
-func (c protoFallbackCodec) Marshal(v interface{}) ([]byte, error) {
-       b, err := c.Fory.Marshal(v)
-       if err != nil {
-               return nil, err
+func fdlUnionResponse(req *grpc_fdl.GrpcFdlRequest, tag string, offset int) 
*grpc_fdl.GrpcFdlUnion {
+       return fdlResponseUnion(fdlResponse(req, tag, offset))
+}
+
+func fdlUnionAggregate(unions []*grpc_fdl.GrpcFdlUnion) 
(*grpc_fdl.GrpcFdlUnion, error) {
+       requests := make([]*grpc_fdl.GrpcFdlRequest, len(unions))
+       for i, union := range unions {
+               req, err := fdlRequestFromUnion(union)
+               if err != nil {
+                       return nil, err
+               }
+               requests[i] = req
+       }
+       return fdlResponseUnion(fdlAggregate(requests)), nil
+}
+
+func fdlRequestFromUnion(union *grpc_fdl.GrpcFdlUnion) 
(*grpc_fdl.GrpcFdlRequest, error) {
+       if union == nil {
+               return nil, fmt.Errorf("expected request union, got nil")
        }
-       out := make([]byte, len(b))
-       copy(out, b)
-       return out, nil
+       req, ok := union.AsRequest()
+       if !ok {
+               return nil, fmt.Errorf("expected request union, got case %d", 
union.Case())
+       }
+       return req, nil
 }
 
-func (c protoFallbackCodec) Unmarshal(data []byte, v interface{}) error {
-       return c.Fory.Unmarshal(data, v)
+func expectUnionResponse(name string, got *grpc_fdl.GrpcFdlUnion, want 
*grpc_fdl.GrpcFdlUnion) error {
+       if got == nil || want == nil {
+               return fmt.Errorf("%s: got %+v, want %+v", name, got, want)
+       }
+       gotResponse, ok := got.AsResponse()
+       if !ok {
+               return fmt.Errorf("%s: got non-response union case %d", name, 
got.Case())
+       }
+       wantResponse, ok := want.AsResponse()
+       if !ok {
+               return fmt.Errorf("%s: want non-response union case %d", name, 
want.Case())
+       }
+       if *gotResponse != *wantResponse {
+               return fmt.Errorf("%s: got %+v, want %+v", name, gotResponse, 
wantResponse)
+       }
+       return nil
 }
 
 // --- server -----------------------------------------------------------------
@@ -144,19 +168,72 @@ func (s *fdlService) BidiStreamMessage(stream 
grpc_fdl.FdlGrpcService_BidiStream
        }
 }
 
-func runServer(portFile string, f *fory.Fory) error {
+func (s *fdlService) UnaryUnion(_ context.Context, req *grpc_fdl.GrpcFdlUnion) 
(*grpc_fdl.GrpcFdlUnion, error) {
+       message, err := fdlRequestFromUnion(req)
+       if err != nil {
+               return nil, err
+       }
+       return fdlUnionResponse(message, "unary", 10), nil
+}
+
+func (s *fdlService) ServerStreamUnion(req *grpc_fdl.GrpcFdlUnion, stream 
grpc_fdl.FdlGrpcService_ServerStreamUnionServer) error {
+       message, err := fdlRequestFromUnion(req)
+       if err != nil {
+               return err
+       }
+       for i := 0; i < 3; i++ {
+               if err := stream.Send(fdlUnionResponse(message, 
fmt.Sprintf("server-%d", i), i)); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (s *fdlService) ClientStreamUnion(stream 
grpc_fdl.FdlGrpcService_ClientStreamUnionServer) error {
+       var requests []*grpc_fdl.GrpcFdlUnion
+       for {
+               req, err := stream.Recv()
+               if err == io.EOF {
+                       response, err := fdlUnionAggregate(requests)
+                       if err != nil {
+                               return err
+                       }
+                       return stream.SendAndClose(response)
+               }
+               if err != nil {
+                       return err
+               }
+               requests = append(requests, req)
+       }
+}
+
+func (s *fdlService) BidiStreamUnion(stream 
grpc_fdl.FdlGrpcService_BidiStreamUnionServer) error {
+       index := 0
+       for {
+               req, err := stream.Recv()
+               if err == io.EOF {
+                       return nil
+               }
+               if err != nil {
+                       return err
+               }
+               message, err := fdlRequestFromUnion(req)
+               if err != nil {
+                       return err
+               }
+               if err := stream.Send(fdlUnionResponse(message, 
fmt.Sprintf("bidi-%d", index), index)); err != nil {
+                       return err
+               }
+               index++
+       }
+}
+
+func runServer(portFile string) error {
        lis, err := net.Listen("tcp", "127.0.0.1:0")
        if err != nil {
                return fmt.Errorf("listen: %w", err)
        }
-       codec := grpc_fdl.CodecV2{Fory: f}
-       // "fory" (v2): used by Go clients via grpc.ForceCodecV2.
-       encoding.RegisterCodecV2(codec)
-       // "proto" (v1): overrides the built-in proto codec so Java clients are
-       // also handled by Fory. RegisterCodecV2 writes to a separate registry 
and
-       // does not replace the v1 "proto" entry; RegisterCodec must be used.
-       encoding.RegisterCodec(protoFallbackCodec{codec})
-       s := grpc.NewServer()
+       s := grpc.NewServer(grpc.ForceServerCodecV2(grpc_fdl.CodecV2{}))
        grpc_fdl.RegisterFdlGrpcServiceServer(s, &fdlService{})
 
        // Write the bound port so the Java test harness knows where to connect.
@@ -246,19 +323,123 @@ func exerciseMessageStub(stub 
grpc_fdl.FdlGrpcServiceClient, requests []*grpc_fd
        return nil
 }
 
-func runClient(target string, f *fory.Fory) error {
+func exerciseUnionStub(stub grpc_fdl.FdlGrpcServiceClient, requests 
[]*grpc_fdl.GrpcFdlUnion) error {
+       ctx := context.Background()
+       first := requests[0]
+       firstMessage, err := fdlRequestFromUnion(first)
+       if err != nil {
+               return err
+       }
+
+       // unary
+       got, err := stub.UnaryUnion(ctx, first)
+       if err != nil {
+               return fmt.Errorf("UnaryUnion: %w", err)
+       }
+       if err := expectUnionResponse("UnaryUnion", got, 
fdlUnionResponse(firstMessage, "unary", 10)); err != nil {
+               return err
+       }
+
+       // server streaming
+       ss, err := stub.ServerStreamUnion(ctx, first)
+       if err != nil {
+               return fmt.Errorf("ServerStreamUnion: %w", err)
+       }
+       for i := 0; i < 3; i++ {
+               got, err := ss.Recv()
+               if err != nil {
+                       return fmt.Errorf("ServerStreamUnion Recv[%d]: %w", i, 
err)
+               }
+               if err := expectUnionResponse(
+                       fmt.Sprintf("ServerStreamUnion[%d]", i),
+                       got,
+                       fdlUnionResponse(firstMessage, fmt.Sprintf("server-%d", 
i), i),
+               ); err != nil {
+                       return err
+               }
+       }
+       if _, err := ss.Recv(); err != io.EOF {
+               return fmt.Errorf("ServerStreamUnion: expected EOF, got %v", 
err)
+       }
+
+       // client streaming
+       cs, err := stub.ClientStreamUnion(ctx)
+       if err != nil {
+               return fmt.Errorf("ClientStreamUnion: %w", err)
+       }
+       for _, req := range requests {
+               if err := cs.Send(req); err != nil {
+                       return fmt.Errorf("ClientStreamUnion Send: %w", err)
+               }
+       }
+       csResp, err := cs.CloseAndRecv()
+       if err != nil {
+               return fmt.Errorf("ClientStreamUnion CloseAndRecv: %w", err)
+       }
+       wantAggregate, err := fdlUnionAggregate(requests)
+       if err != nil {
+               return err
+       }
+       if err := expectUnionResponse("ClientStreamUnion", csResp, 
wantAggregate); err != nil {
+               return err
+       }
+
+       // bidirectional streaming
+       bidi, err := stub.BidiStreamUnion(ctx)
+       if err != nil {
+               return fmt.Errorf("BidiStreamUnion: %w", err)
+       }
+       for i, req := range requests {
+               message, err := fdlRequestFromUnion(req)
+               if err != nil {
+                       return err
+               }
+               if err := bidi.Send(req); err != nil {
+                       return fmt.Errorf("BidiStreamUnion Send[%d]: %w", i, 
err)
+               }
+               got, err := bidi.Recv()
+               if err != nil {
+                       return fmt.Errorf("BidiStreamUnion Recv[%d]: %w", i, 
err)
+               }
+               if err := expectUnionResponse(
+                       fmt.Sprintf("BidiStreamUnion[%d]", i),
+                       got,
+                       fdlUnionResponse(message, fmt.Sprintf("bidi-%d", i), i),
+               ); err != nil {
+                       return err
+               }
+       }
+       if err := bidi.CloseSend(); err != nil {
+               return fmt.Errorf("BidiStreamUnion CloseSend: %w", err)
+       }
+       if _, err := bidi.Recv(); err != io.EOF {
+               return fmt.Errorf("BidiStreamUnion: expected EOF after 
CloseSend, got %v", err)
+       }
+
+       return nil
+}
+
+func runClient(target string) error {
        conn, err := grpc.NewClient(target, 
grpc.WithTransportCredentials(insecure.NewCredentials()))
        if err != nil {
                return fmt.Errorf("dial %s: %w", target, err)
        }
        defer conn.Close()
 
-       stub := grpc_fdl.NewFdlGrpcServiceClient(conn, f)
+       stub := grpc_fdl.NewFdlGrpcServiceClient(conn)
        requests := []*grpc_fdl.GrpcFdlRequest{
                {Id: "fdl-a", Count: 1, Payload: "alpha"},
                {Id: "fdl-b", Count: 2, Payload: "beta"},
        }
-       return exerciseMessageStub(stub, requests)
+       if err := exerciseMessageStub(stub, requests); err != nil {
+               return err
+       }
+
+       unionRequests := []*grpc_fdl.GrpcFdlUnion{
+               fdlRequestUnion(&grpc_fdl.GrpcFdlRequest{Id: "fdl-u-a", Count: 
3, Payload: "union-alpha"}),
+               fdlRequestUnion(&grpc_fdl.GrpcFdlRequest{Id: "fdl-u-b", Count: 
4, Payload: "union-beta"}),
+       }
+       return exerciseUnionStub(stub, unionRequests)
 }
 
 // --- entry point ------------------------------------------------------------
@@ -274,15 +455,13 @@ func main() {
                log.Fatal("usage: grpc-interop <server|client> [flags]")
        }
 
-       f := newFory()
-
        switch os.Args[1] {
        case "server":
                serverCmd.Parse(os.Args[2:])
                if *serverPortFile == "" {
                        log.Fatal("--port-file is required")
                }
-               if err := runServer(*serverPortFile, f); err != nil {
+               if err := runServer(*serverPortFile); err != nil {
                        log.Fatalf("server error: %v", err)
                }
        case "client":
@@ -290,7 +469,7 @@ func main() {
                if *clientTarget == "" {
                        log.Fatal("--target is required")
                }
-               if err := runClient(*clientTarget, f); err != nil {
+               if err := runClient(*clientTarget); err != nil {
                        log.Fatalf("client error: %v", err)
                }
        default:
diff --git 
a/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GoGrpcInteropTest.java
 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GoGrpcInteropTest.java
new file mode 100644
index 000000000..44d9b9c03
--- /dev/null
+++ 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GoGrpcInteropTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.grpc_tests;
+
+import io.grpc.Server;
+import java.util.concurrent.TimeUnit;
+import org.testng.annotations.Test;
+
+public class GoGrpcInteropTest extends GrpcTestBase {
+
+  @Test
+  public void testJavaServerGoClient() throws Exception {
+    Server server = startJavaFdlServer();
+    try {
+      runGo("go-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
+    } finally {
+      server.shutdownNow();
+      server.awaitTermination(10, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void testGoServerJavaClient() throws Exception {
+    exercisePeerServer("go-grpc", "Go", "fory-grpc-go-", goCommand("server"), 
this::exerciseFdl);
+  }
+}
diff --git 
a/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcInteropTest.java
 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcTestBase.java
similarity index 85%
rename from 
integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcInteropTest.java
rename to 
integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcTestBase.java
index bbf6a8629..730bfbf95 100644
--- 
a/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcInteropTest.java
+++ 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/GrpcTestBase.java
@@ -40,94 +40,52 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import org.testng.Assert;
-import org.testng.annotations.Test;
-
-public class GrpcInteropTest {
-
-  @Test
-  public void testJavaServerPythonClient() throws Exception {
-    Server server =
-        ServerBuilder.forPort(0)
-            .addService(new FdlService())
-            .addService(new FbsService())
-            .addService(new PbService())
-            .build()
-            .start();
-    try {
-      runPython("python-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
-    } finally {
-      server.shutdownNow();
-      server.awaitTermination(10, TimeUnit.SECONDS);
-    }
+
+public abstract class GrpcTestBase {
+
+  @FunctionalInterface
+  protected interface ChannelExercise {
+    void run(ManagedChannel channel) throws Exception;
   }
 
-  @Test
-  public void testJavaClientPythonServer() throws Exception {
-    Path portFile = Files.createTempFile("fory-grpc-python-", ".port");
-    Files.deleteIfExists(portFile);
-    PeerCommand command = pythonCommand("server", "--port-file", 
portFile.toString());
-    Process process = startPeer(command);
-    PeerOutputCollector outputCollector =
-        new PeerOutputCollector(process.getInputStream(), 
"python-grpc-server");
-    outputCollector.start();
-    try {
-      int port = waitForPort(process, outputCollector, portFile, "Python");
-      ManagedChannel channel =
-          ManagedChannelBuilder.forAddress("127.0.0.1", 
port).usePlaintext().build();
-      try {
-        exerciseFdl(channel);
-        exerciseFbs(channel);
-        exercisePb(channel);
-      } finally {
-        channel.shutdownNow();
-        channel.awaitTermination(10, TimeUnit.SECONDS);
-      }
-    } finally {
-      process.destroy();
-      process.waitFor(10, TimeUnit.SECONDS);
-      if (process.isAlive()) {
-        process.destroyForcibly();
-        process.waitFor(10, TimeUnit.SECONDS);
-      }
-      outputCollector.awaitOutput();
-      Files.deleteIfExists(portFile);
-    }
+  protected Server startJavaAllSchemasServer() throws IOException {
+    return ServerBuilder.forPort(0)
+        .addService(new FdlService())
+        .addService(new FbsService())
+        .addService(new PbService())
+        .build()
+        .start();
   }
 
-  @Test
-  public void testJavaServerRustClient() throws Exception {
-    Server server =
-        ServerBuilder.forPort(0)
-            .addService(new FdlService())
-            .addService(new FbsService())
-            .addService(new PbService())
-            .build()
-            .start();
-    try {
-      runRust("rust-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
-    } finally {
-      server.shutdownNow();
-      server.awaitTermination(10, TimeUnit.SECONDS);
-    }
+  protected Server startJavaFdlServer() throws IOException {
+    return ServerBuilder.forPort(0).addService(new 
FdlService()).build().start();
   }
 
-  @Test
-  public void testJavaClientRustServer() throws Exception {
-    Path portFile = Files.createTempFile("fory-grpc-rust-", ".port");
+  protected void exerciseAllSchemas(ManagedChannel channel) throws 
InterruptedException {
+    exerciseFdl(channel);
+    exerciseFbs(channel);
+    exercisePb(channel);
+  }
+
+  protected void exercisePeerServer(
+      String peer,
+      String language,
+      String portPrefix,
+      PeerCommand command,
+      ChannelExercise exercise)
+      throws Exception {
+    Path portFile = Files.createTempFile(portPrefix, ".port");
     Files.deleteIfExists(portFile);
-    PeerCommand command = rustCommand("server", "--port-file", 
portFile.toString());
-    Process process = startPeer(command);
+    Process process = startPeer(command.withPortFile(portFile));
     PeerOutputCollector outputCollector =
-        new PeerOutputCollector(process.getInputStream(), "rust-grpc-server");
+        new PeerOutputCollector(process.getInputStream(), peer + "-server");
     outputCollector.start();
     try {
-      int port = waitForPort(process, outputCollector, portFile, "Rust");
+      int port = waitForPort(process, outputCollector, portFile, language);
       ManagedChannel channel =
           ManagedChannelBuilder.forAddress("127.0.0.1", 
port).usePlaintext().build();
       try {
-        exerciseFdl(channel);
-        exerciseFbs(channel);
-        exercisePb(channel);
+        exercise.run(channel);
       } finally {
         channel.shutdownNow();
         channel.awaitTermination(10, TimeUnit.SECONDS);
@@ -144,7 +102,7 @@ public class GrpcInteropTest {
     }
   }
 
-  private void exerciseFdl(ManagedChannel channel) throws InterruptedException 
{
+  protected void exerciseFdl(ManagedChannel channel) throws 
InterruptedException {
     grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceBlockingStub blocking =
         grpc_fdl.FdlGrpcServiceGrpc.newBlockingStub(channel);
     grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceStub async =
@@ -161,18 +119,6 @@ public class GrpcInteropTest {
     assertFdlUnions(blocking, async, unions);
   }
 
-  // exerciseFdlMessages exercises the four FDL message streaming modes 
without union methods.
-  private void exerciseFdlMessages(ManagedChannel channel) throws 
InterruptedException {
-    grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceBlockingStub blocking =
-        grpc_fdl.FdlGrpcServiceGrpc.newBlockingStub(channel);
-    grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceStub async =
-        grpc_fdl.FdlGrpcServiceGrpc.newStub(channel);
-
-    List<grpc_fdl.GrpcFdlRequest> messages =
-        Arrays.asList(fdlRequest("fdl-a", 1, "alpha"), fdlRequest("fdl-b", 2, 
"beta"));
-    assertFdlMessages(blocking, async, messages);
-  }
-
   private void assertFdlMessages(
       grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceBlockingStub blocking,
       grpc_fdl.FdlGrpcServiceGrpc.FdlGrpcServiceStub async,
@@ -227,7 +173,7 @@ public class GrpcInteropTest {
             fdlUnionResponse(fdlRequestFromUnion(requests.get(1)), "bidi-1", 
1)));
   }
 
-  private void exerciseFbs(ManagedChannel channel) throws InterruptedException 
{
+  protected void exerciseFbs(ManagedChannel channel) throws 
InterruptedException {
     grpc_fbs.FbsGrpcServiceGrpc.FbsGrpcServiceBlockingStub blocking =
         grpc_fbs.FbsGrpcServiceGrpc.newBlockingStub(channel);
     grpc_fbs.FbsGrpcServiceGrpc.FbsGrpcServiceStub async =
@@ -298,7 +244,7 @@ public class GrpcInteropTest {
             fbsUnionResponse(fbsRequestFromUnion(requests.get(1)), "bidi-1", 
1)));
   }
 
-  private void exercisePb(ManagedChannel channel) throws InterruptedException {
+  protected void exercisePb(ManagedChannel channel) throws 
InterruptedException {
     grpc_pb.PbGrpcServiceGrpc.PbGrpcServiceBlockingStub blocking =
         grpc_pb.PbGrpcServiceGrpc.newBlockingStub(channel);
     grpc_pb.PbGrpcServiceGrpc.PbGrpcServiceStub async = 
grpc_pb.PbGrpcServiceGrpc.newStub(channel);
@@ -328,49 +274,7 @@ public class GrpcInteropTest {
             pbResponse(requests.get(0), "bidi-0", 0), 
pbResponse(requests.get(1), "bidi-1", 1)));
   }
 
-  @Test
-  public void testJavaServerGoClient() throws Exception {
-    Server server = ServerBuilder.forPort(0).addService(new 
FdlService()).build().start();
-    try {
-      runGo("go-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
-    } finally {
-      server.shutdownNow();
-      server.awaitTermination(10, TimeUnit.SECONDS);
-    }
-  }
-
-  @Test
-  public void testGoServerJavaClient() throws Exception {
-    Path portFile = Files.createTempFile("fory-grpc-go-", ".port");
-    Files.deleteIfExists(portFile);
-    PeerCommand command = goCommand("server", "--port-file", 
portFile.toString());
-    Process process = startPeer(command);
-    PeerOutputCollector outputCollector =
-        new PeerOutputCollector(process.getInputStream(), "go-grpc-server");
-    outputCollector.start();
-    try {
-      int port = waitForPort(process, outputCollector, portFile, "Go");
-      ManagedChannel channel =
-          ManagedChannelBuilder.forAddress("127.0.0.1", 
port).usePlaintext().build();
-      try {
-        exerciseFdlMessages(channel);
-      } finally {
-        channel.shutdownNow();
-        channel.awaitTermination(10, TimeUnit.SECONDS);
-      }
-    } finally {
-      process.destroy();
-      process.waitFor(10, TimeUnit.SECONDS);
-      if (process.isAlive()) {
-        process.destroyForcibly();
-        process.waitFor(10, TimeUnit.SECONDS);
-      }
-      outputCollector.awaitOutput();
-      Files.deleteIfExists(portFile);
-    }
-  }
-
-  private PeerCommand goCommand(String... args) {
+  protected PeerCommand goCommand(String... args) {
     Path grpcRoot = 
repoRoot().resolve("integration_tests").resolve("grpc_tests");
     Path goRoot = grpcRoot.resolve("go");
     List<String> command = new ArrayList<>();
@@ -384,7 +288,7 @@ public class GrpcInteropTest {
     return peerCommand;
   }
 
-  private void runGo(String peer, String... args) throws IOException, 
InterruptedException {
+  protected void runGo(String peer, String... args) throws IOException, 
InterruptedException {
     Process process = startPeer(goCommand(args));
     PeerOutputCollector outputCollector = new 
PeerOutputCollector(process.getInputStream(), peer);
     outputCollector.start();
@@ -406,7 +310,7 @@ public class GrpcInteropTest {
     outputCollector.awaitOutput();
   }
 
-  private PeerCommand pythonCommand(String... args) {
+  protected PeerCommand pythonCommand(String... args) {
     Path repoRoot = repoRoot();
     Path grpcRoot = 
repoRoot.resolve("integration_tests").resolve("grpc_tests");
     Path pythonRoot = grpcRoot.resolve("python");
@@ -443,7 +347,7 @@ public class GrpcInteropTest {
     return peerCommand;
   }
 
-  private PeerCommand rustCommand(String... args) {
+  protected PeerCommand rustCommand(String... args) {
     Path repoRoot = repoRoot();
     Path grpcRoot = 
repoRoot.resolve("integration_tests").resolve("grpc_tests");
     Path rustRoot = grpcRoot.resolve("rust");
@@ -471,7 +375,7 @@ public class GrpcInteropTest {
     return peerCommand;
   }
 
-  private void runPython(String peer, String... args) throws IOException, 
InterruptedException {
+  protected void runPython(String peer, String... args) throws IOException, 
InterruptedException {
     Process process = startPeer(pythonCommand(args));
     PeerOutputCollector outputCollector = new 
PeerOutputCollector(process.getInputStream(), peer);
     outputCollector.start();
@@ -493,7 +397,7 @@ public class GrpcInteropTest {
     outputCollector.awaitOutput();
   }
 
-  private void runRust(String peer, String... args) throws IOException, 
InterruptedException {
+  protected void runRust(String peer, String... args) throws IOException, 
InterruptedException {
     Process process = startPeer(rustCommand(args));
     PeerOutputCollector outputCollector = new 
PeerOutputCollector(process.getInputStream(), peer);
     outputCollector.start();
@@ -589,7 +493,7 @@ public class GrpcInteropTest {
 
   private static grpc_fdl.GrpcFdlUnion 
fdlUnionAggregate(List<grpc_fdl.GrpcFdlUnion> unions) {
     return grpc_fdl.GrpcFdlUnion.ofResponse(
-        fdlAggregate(map(unions, GrpcInteropTest::fdlRequestFromUnion)));
+        fdlAggregate(map(unions, GrpcTestBase::fdlRequestFromUnion)));
   }
 
   private static grpc_fdl.GrpcFdlRequest 
fdlRequestFromUnion(grpc_fdl.GrpcFdlUnion union) {
@@ -629,7 +533,7 @@ public class GrpcInteropTest {
 
   private static grpc_fbs.GrpcFbsUnion 
fbsUnionAggregate(List<grpc_fbs.GrpcFbsUnion> unions) {
     return grpc_fbs.GrpcFbsUnion.ofGrpcFbsResponse(
-        fbsAggregate(map(unions, GrpcInteropTest::fbsRequestFromUnion)));
+        fbsAggregate(map(unions, GrpcTestBase::fbsRequestFromUnion)));
   }
 
   private static grpc_fbs.GrpcFbsRequest 
fbsRequestFromUnion(grpc_fbs.GrpcFbsUnion union) {
@@ -795,7 +699,7 @@ public class GrpcInteropTest {
     @Override
     public StreamObserver<grpc_fdl.GrpcFdlRequest> clientStreamMessage(
         StreamObserver<grpc_fdl.GrpcFdlResponse> responseObserver) {
-      return collectAndRespond(responseObserver, 
GrpcInteropTest::fdlAggregate);
+      return collectAndRespond(responseObserver, GrpcTestBase::fdlAggregate);
     }
 
     @Override
@@ -843,7 +747,7 @@ public class GrpcInteropTest {
     @Override
     public StreamObserver<grpc_fdl.GrpcFdlUnion> clientStreamUnion(
         StreamObserver<grpc_fdl.GrpcFdlUnion> responseObserver) {
-      return collectAndRespond(responseObserver, 
GrpcInteropTest::fdlUnionAggregate);
+      return collectAndRespond(responseObserver, 
GrpcTestBase::fdlUnionAggregate);
     }
 
     @Override
@@ -895,7 +799,7 @@ public class GrpcInteropTest {
     @Override
     public StreamObserver<grpc_fbs.GrpcFbsRequest> clientStreamMessage(
         StreamObserver<grpc_fbs.GrpcFbsResponse> responseObserver) {
-      return collectAndRespond(responseObserver, 
GrpcInteropTest::fbsAggregate);
+      return collectAndRespond(responseObserver, GrpcTestBase::fbsAggregate);
     }
 
     @Override
@@ -943,7 +847,7 @@ public class GrpcInteropTest {
     @Override
     public StreamObserver<grpc_fbs.GrpcFbsUnion> clientStreamUnion(
         StreamObserver<grpc_fbs.GrpcFbsUnion> responseObserver) {
-      return collectAndRespond(responseObserver, 
GrpcInteropTest::fbsUnionAggregate);
+      return collectAndRespond(responseObserver, 
GrpcTestBase::fbsUnionAggregate);
     }
 
     @Override
@@ -993,7 +897,7 @@ public class GrpcInteropTest {
     @Override
     public StreamObserver<grpc_pb.GrpcPbRequest> clientStreamMessage(
         StreamObserver<grpc_pb.GrpcPbResponse> responseObserver) {
-      return collectAndRespond(responseObserver, GrpcInteropTest::pbAggregate);
+      return collectAndRespond(responseObserver, GrpcTestBase::pbAggregate);
     }
 
     @Override
@@ -1053,10 +957,21 @@ public class GrpcInteropTest {
     }
   }
 
-  private static final class PeerCommand {
+  protected static final class PeerCommand {
     private List<String> command;
     private Path workDir;
     private final java.util.Map<String, String> environment = new 
java.util.HashMap<>();
+
+    private PeerCommand withPortFile(Path portFile) {
+      List<String> serverCommand = new ArrayList<>(command);
+      serverCommand.add("--port-file");
+      serverCommand.add(portFile.toString());
+      PeerCommand peerCommand = new PeerCommand();
+      peerCommand.command = serverCommand;
+      peerCommand.workDir = workDir;
+      peerCommand.environment.putAll(environment);
+      return peerCommand;
+    }
   }
 
   private static final class PeerOutputCollector extends Thread {
diff --git 
a/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/PythonGrpcInteropTest.java
 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/PythonGrpcInteropTest.java
new file mode 100644
index 000000000..676defdaa
--- /dev/null
+++ 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/PythonGrpcInteropTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.grpc_tests;
+
+import io.grpc.Server;
+import java.util.concurrent.TimeUnit;
+import org.testng.annotations.Test;
+
+public class PythonGrpcInteropTest extends GrpcTestBase {
+
+  @Test
+  public void testJavaServerPythonClient() throws Exception {
+    Server server = startJavaAllSchemasServer();
+    try {
+      runPython("python-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
+    } finally {
+      server.shutdownNow();
+      server.awaitTermination(10, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void testJavaClientPythonServer() throws Exception {
+    exercisePeerServer(
+        "python-grpc",
+        "Python",
+        "fory-grpc-python-",
+        pythonCommand("server"),
+        this::exerciseAllSchemas);
+  }
+}
diff --git 
a/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/RustGrpcInteropTest.java
 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/RustGrpcInteropTest.java
new file mode 100644
index 000000000..6dae4a745
--- /dev/null
+++ 
b/integration_tests/grpc_tests/java/src/test/java/org/apache/fory/grpc_tests/RustGrpcInteropTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.grpc_tests;
+
+import io.grpc.Server;
+import java.util.concurrent.TimeUnit;
+import org.testng.annotations.Test;
+
+public class RustGrpcInteropTest extends GrpcTestBase {
+
+  @Test
+  public void testJavaServerRustClient() throws Exception {
+    Server server = startJavaAllSchemasServer();
+    try {
+      runRust("rust-grpc-client", "client", "--target", "127.0.0.1:" + 
server.getPort());
+    } finally {
+      server.shutdownNow();
+      server.awaitTermination(10, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void testJavaClientRustServer() throws Exception {
+    exercisePeerServer(
+        "rust-grpc", "Rust", "fory-grpc-rust-", rustCommand("server"), 
this::exerciseAllSchemas);
+  }
+}
diff --git a/integration_tests/grpc_tests/run_tests.sh 
b/integration_tests/grpc_tests/run_tests.sh
index 22b9cd3aa..8028b3477 100755
--- a/integration_tests/grpc_tests/run_tests.sh
+++ b/integration_tests/grpc_tests/run_tests.sh
@@ -21,6 +21,7 @@ set -euo pipefail
 
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+TEST_CLASSES="${1:-PythonGrpcInteropTest,RustGrpcInteropTest,GoGrpcInteropTest}"
 
 python -m pip install "grpcio>=1.62.2,<1.71"
 python -m pip install -v -e "${ROOT_DIR}/python"
@@ -32,5 +33,5 @@ go build -o grpc-interop .
 cargo build --manifest-path "${SCRIPT_DIR}/rust/Cargo.toml" --workspace --quiet
 cd "${ROOT_DIR}/integration_tests/grpc_tests/java"
 mvn -T16 --no-transfer-progress \
-  -Dtest=GrpcInteropTest \
+  -Dtest="${TEST_CLASSES}" \
   test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to