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
- Verify registration: Does your server code call
RegisterMyServiceServer()(Go) oraddService()(Node/Java)? - Check the proto: Are client and server built from the same
.protofile? Especially thepackageandservicenames. - 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?
- Use grpcurl: List the services and methods the server actually exposes. Compare to what the client calls.
- Check for rewrites: If there's a gateway or proxy, do its routing rules preserve the full path?
- Review version history: Did a recent deploy change the proto or remove a method?
- 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.