Fix Common Errors

gRPC UNIMPLEMENTED (Code 12): Why Your Method Isn't Found

gRPC status 12 UNIMPLEMENTED usually means a proto/route mismatch, not missing code. Debug service names, proxies, and reflection to fix it fast.

When your gRPC client hits a UNIMPLEMENTED error, the first instinct is usually "oh, the developer forgot to write the method." Almost always, that's wrong. A gRPC status 12 error means your server received a valid gRPC request, examined the method path, and found no handler registered for it — a routing and registration problem, not a missing implementation. This guide shows you why that happens and how to fix it.

The gRPC wire format sends a request as an HTTP/2 POST to a path like /package.Service/Method. If that path doesn't match any registered handler, the server returns UNIMPLEMENTED. Your task is to make sure the server actually registered the service, the paths match exactly, and the client and server built from compatible proto definitions.

Understanding UNIMPLEMENTED: routing, not implementation

gRPC UNIMPLEMENTED is a routing error. The server's gRPC runtime successfully parsed the request and determined the method path — /your.package.YourService/YourMethod — but no handler was registered under that exact path. This is distinct from a method that exists but hasn't run yet, or a method that throws a business logic error.

The path itself is critical. It's always /package.Service/Method, where package is the proto package directive, Service is the service name, and Method is the RPC method name. If any of these three parts mashes between client and server, the server won't find a match.

The gRPC specification calls this the RPC path. See grpc-status-codes-explained for how UNIMPLEMENTED compares to other status codes like UNAVAILABLE and INTERNAL.

Common causes of UNIMPLEMENTED

Service not registered on the server

The most direct cause: you created a service stub and handler, but never registered it with the gRPC server. Here's what correct registration looks like in a few languages:

Go:

import "google.golang.org/grpc"
import pb "myapp/api" // your generated protobuf package

server := grpc.NewServer()
pb.RegisterMyServiceServer(server, &myServiceImpl{})
server.Serve(listener)

Node.js (with @grpc/grpc-js):

const grpc = require("@grpc/grpc-js");
const {MyServiceService} = require("./api/my_service_grpc_pb.js");
const impl = require("./implementation.js");

const server = new grpc.Server();
server.addService(MyServiceService, impl);
server.bindAsync("0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), () => {
  server.start();
});

Java (with gRPC):

import io.grpc.Server;
import io.grpc.ServerBuilder;
import com.example.api.MyServiceGrpc;

Server server = ServerBuilder.forPort(50051)
  .addService(new MyServiceImpl())
  .build();
server.start();

If you skip the registration step, even a perfectly implemented handler is unreachable.

Proto package name mismatch

Your .proto file starts with:

syntax = "proto3";
package my.api.v1;
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse) {}
}

The full RPC path your client must call is /my.api.v1.UserService/GetUser. If your client was built from a different proto that says package api.v1, the path becomes /api.v1.UserService/GetUser — a mismatch, and UNIMPLEMENTED.

Always verify that client and server were generated from the exact same .proto file (or at least proto files that define the same package and service names).

Client and server built from different proto versions

A classic scenario: you update the proto definition, regenerate server code, but forget to regenerate the client. The old client might call /old.package.Service/Method while the server only knows /new.package.Service/Method. Or the method name changed. Check git history; a silent proto update is easy to miss.

Gateway or proxy stripping the path

Some setups use a gRPC-HTTP gateway or a reverse proxy in front of the gRPC server. If that gateway is misconfigured, it might rewrite or drop the method path before forwarding to the server. For example, a proxy might strip /api/v1 prefix and forward only /UserService/GetUser instead of /my.api.v1.UserService/GetUser.

If you have a gateway, check its routing rules and confirm paths pass through unmodified.

Calling a gRPC service over HTTP/1.1 or REST

A subtle but common mistake: you pointed your client at a REST endpoint thinking it was gRPC, or the server is only exposing a REST API on that port, not gRPC. gRPC requires HTTP/2. If the connection downgrades to HTTP/1.1, the server won't even try to parse it as gRPC.

Verify your server is actually listening for gRPC (HTTP/2) on the port you're connecting to.

gRPC-Web without a translating proxy

gRPC-Web is a browser variant that wraps gRPC messages in HTTP/1.1-compatible frames. A browser client cannot speak raw gRPC; it needs a gRPC-Web proxy (like Envoy) between it and the server to translate. If you're calling a standard gRPC server from the browser without a proxy in between, the connection fails.

TLS termination losing HTTP/2

Some load balancers or proxies terminate TLS and forward to the backend over HTTP/1.1, or drop HTTP/2 support entirely. This breaks gRPC. Ensure your TLS terminator supports HTTP/2 and passes it through.

Method genuinely removed or renamed

If you deployed a new server version that renamed or removed an RPC method, older clients calling the old name get UNIMPLEMENTED. Check release notes and version compatibility.

Debugging with grpcurl

The fastest way to find what's actually registered on your server is grpcurl. It's a gRPC client you can run from the command line — like curl for gRPC.

First, enable gRPC reflection on your server (if it's safe to do in your environment; disable in production). Here's how:

Go:

import "google.golang.org/grpc/reflection"

server := grpc.NewServer()
pb.RegisterMyServiceServer(server, &myServiceImpl{})
reflection.Register(server)  // enable reflection
server.Serve(listener)

Node.js:

const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const packageDef = protoLoader.loadSync("api/my_service.proto", {});
const services = grpc.loadPackageDefinition(packageDef);

const server = new grpc.Server();
server.addService(services.my.api.v1.MyService.service, impl);
server.bindAsync(...);

Then list what the server exposes:

grpcurl -plaintext localhost:50051 list
# Output:
# my.api.v1.UserService
# my.api.v1.OrderService

List methods in a service:

grpcurl -plaintext localhost:50051 list my.api.v1.UserService
# Output:
# my.api.v1.UserService.GetUser
# my.api.v1.UserService.CreateUser

Describe a method to see its full signature:

grpcurl -plaintext localhost:50051 describe my.api.v1.UserService.GetUser

What to look for:

  • Is the service listed at all? If not, it's not registered.
  • Is the service name spelled correctly? Case matters.
  • Is the package name correct? Compare to your client's proto.
  • Is the method name correct?

If grpcurl list shows nothing or doesn't match what your client expects, your server's routing is wrong. If it does match and your client still gets UNIMPLEMENTED, the problem is in the client or the network path between them.

gRPC reflection leaks your service structure to anyone who can reach the port. Disable it in production and only enable for local debugging or protected environments.

Wire-format perspective

At the HTTP/2 level, a gRPC request is a POST to a path with specific headers. The method path is immutable — it's part of the URL. If the server's routing layer (the gRPC framework itself, before your code runs) cannot map that path to a handler, UNIMPLEMENTED fires before your service code ever runs.

This is why the error is so deterministic: the server isn't going to look up the method dynamically at runtime. Registration is compile-time and startup-time. Mismatched paths, proto versions, or skipped registration all result in the same outcome: no handler at that path, UNIMPLEMENTED.

Troubleshooting checklist

  1. Verify registration: Does your server code call RegisterMyServiceServer() (Go) or addService() (Node/Java)?
  2. Check the proto: Are client and server built from the same .proto file? Especially the package and service names.
  3. Confirm the port: Is the server actually listening on the port you're connecting to? Is it gRPC (HTTP/2), not HTTP/1.1 or REST?
  4. Use grpcurl: List the services and methods the server actually exposes. Compare to what the client calls.
  5. Check for rewrites: If there's a gateway or proxy, do its routing rules preserve the full path?
  6. Review version history: Did a recent deploy change the proto or remove a method?
  7. Inspect headers and TLS: Confirm HTTP/2 is negotiated and TLS isn't being stripped to HTTP/1.1.

For deeper strategies on hunting production errors, see how to debug production errors. For other gRPC error codes, check grpc status codes explained, and for UNIMPLEMENTED's cousins, see grpc internal error code 13 and grpc unavailable error code 14.

When an UNIMPLEMENTED error lands in production, the distributed tracing and error grouping tools make it obvious which client calls are hitting which path — and whether a code change or deployment went wrong.

Start tracking errors in minutes

Track every gRPC error, including UNIMPLEMENTED, with spans and stack traces so you see exactly which client hit which path and why the server didn't route it.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.