Chaincode is where business logic meets the blockchain. Every asset transfer, every access control check, every compliance rule in a Hyperledger Fabric network runs as chaincode. According to the Hyperledger Foundation's 2024 survey, Go accounts for approximately 68% of production chaincode deployments, followed by JavaScript/Node.js at 22% and Java at 10%. But those numbers don't tell the whole story — the right language depends on your team, your performance requirements, and how fast you need to iterate.
I've written chaincode in all three languages across different projects. Go is my default for anything heading to production. JavaScript is what I reach for during rapid prototyping or when working with frontend-heavy teams. Java works well in large enterprise environments where the entire stack is already JVM-based. This tutorial gives you complete, working examples in all three so you can make your own informed choice.
TL;DR: Go is the best default for Fabric chaincode — it delivers the highest throughput (3,500+ TPS in optimized Fabric networks per the Hyperledger Performance Whitepaper, 2024), produces small binaries, and matches Fabric's own codebase. JavaScript/TypeScript wins for rapid prototyping and web-developer teams. Java fits enterprises already invested in JVM infrastructure.
If you already have a Fabric network running, you can skip ahead to the Go tutorial section. If you need a network first, follow our guide to create a Hyperledger Fabric network.
Chaincode is Fabric's term for smart contracts — programs that execute against the shared ledger to read and write state. The Hyperledger Fabric documentation defines chaincode as the business logic that governs how applications interact with the ledger. The key distinction from Ethereum smart contracts is that Fabric chaincode runs in a separate container (or process) rather than inside a virtual machine.
On Ethereum (and Besu), smart contracts are compiled to bytecode and executed inside the EVM. Every validator runs the same bytecode deterministically. On Fabric, chaincode runs in its own Docker container or external process. The peer communicates with it over gRPC.
This architectural difference has practical consequences. Chaincode can use standard language features — file system access, external libraries, network calls (with caveats) — that EVM smart contracts can't. But it also means chaincode must be installed on every endorsing peer separately, and the lifecycle is more complex.
Fabric's chaincode lifecycle has four phases: package, install, approve, and commit. This multi-step process provides governance guardrails that Ethereum deployments lack.
Package — Bundle the chaincode source into a tar.gz archive with metadata
Install — Upload the package to each endorsing peer
Approve — Each organization reviews and approves the chaincode definition
Commit — Once enough organizations approve (per the lifecycle endorsement policy), the chaincode is committed to the channel
This process ensures no single organization can deploy code that affects the shared ledger without multi-party agreement. It's bureaucratic, yes. But for regulated industries, that governance is a feature.
Fabric chaincode runs in isolated containers communicating over gRPC, unlike EVM smart contracts that execute inside a virtual machine. The four-phase lifecycle (package, install, approve, commit) enforces multi-organization governance over code deployments, as specified in the Hyperledger Fabric documentation.
Go dominates production deployments at 68% market share (Hyperledger Foundation, 2024), but language choice should follow team capabilities and project constraints. Here's a detailed comparison across the dimensions that actually matter in enterprise projects.
Pick Go when throughput matters, when you want the smallest attack surface (single binary, no runtime dependencies), or when your team already knows Go. Go chaincode matches Fabric's own language, which means debugging deep into Fabric internals is straightforward. The fabric-contract-api-go library is maintained by the Hyperledger community and stays current with Fabric releases.
Pick JavaScript when your team is web-focused and you need fast iteration cycles. The npm ecosystem gives you access to thousands of utility libraries. TypeScript adds compile-time type checking that catches bugs before deployment. For proof-of-concept work, JavaScript's lower ceremony (no compilation step for plain JS) accelerates the feedback loop.
Pick Java when your organization standardizes on the JVM. Enterprise Java teams bring existing expertise in Maven/Gradle build systems, JUnit testing, and Spring-based dependency injection patterns. Java chaincode integrates naturally with enterprise middleware. The downside is JVM cold-start time and memory footprint.
Free resource
Smart Contract Development Cheat Sheet — Go, JS, Java, and Solidity
Side-by-side code patterns for Fabric chaincode (Go/JS/Java) and Besu smart contracts (Solidity). Includes testing templates and common anti-patterns.
Here's a production-ready CRUD chaincode in Go that manages a generic asset. The fabric-contract-api-go library (v1.2+) provides the contract interface. This example includes input validation, error handling, and JSON serialization — patterns you'll reuse in every Go chaincode you write.
Rich queries are powerful but carry a caveat: they're only available when the peer uses CouchDB as its state database, and they aren't suitable for endorsement-critical operations because CouchDB query results can vary between peers.
Go chaincode accounts for 68% of production Fabric deployments, delivering the highest throughput as native binaries with sub-second cold starts. The fabric-contract-api-go library provides the contract interface, with GetStub() methods for all state operations — PutState, GetState, DelState, and range/composite key queries (Hyperledger Foundation, 2024).
For AI-assisted chaincode generation that produces code in this exact pattern, see our guide on building chaincodes with AI.
The JavaScript/TypeScript contract API mirrors the Go API in structure. The fabric-contract-api npm package (part of the fabric-shim family) handles the gRPC communication with the peer. According to npm download statistics, the package averages over 4,000 weekly downloads in 2026 — a healthy and active ecosystem.
The key TypeScript advantages: compile-time type checking catches parameter mismatches before deployment, and interfaces make the data model explicit. The trade-off is an additional compilation step and slightly more verbose code.
In my experience, TypeScript is worth the overhead for any chaincode that will be maintained by a team larger than two people. The type annotations serve as documentation that the compiler enforces.
Free resource
Smart Contract Development Cheat Sheet — Go, JS, Java, and Solidity
Side-by-side code patterns for Fabric chaincode (Go/JS/Java) and Besu smart contracts (Solidity). Includes testing templates and common anti-patterns.
Java chaincode uses the fabric-chaincode-java library with annotation-based contract definitions. According to Maven Central statistics, the fabric-chaincode-shim artifact sees approximately 1,200 monthly downloads — smaller than the JavaScript ecosystem but stable among enterprise Java shops.
package com.example.chaincode;import org.hyperledger.fabric.contract.ContractInterface;import org.hyperledger.fabric.contract.Context;import org.hyperledger.fabric.contract.annotation.*;import org.hyperledger.fabric.shim.ChaincodeException;import com.google.gson.Gson;import java.time.Instant;@Contract(name = "AssetContract")@Defaultpublic class AssetContract implements ContractInterface { private final Gson gson = new Gson(); @Transaction(intent = Transaction.TYPE.SUBMIT) public String CreateAsset(Context ctx, String id, String owner, int value) { byte[] existing = ctx.getStub().getState(id); if (existing != null && existing.length > 0) { throw new ChaincodeException( "Asset " + id + " already exists" ); } String now = Instant.now().toString(); Asset asset = new Asset(id, owner, value, "active", now, now); String json = gson.toJson(asset); ctx.getStub().putStringState(id, json); return json; } @Transaction(intent = Transaction.TYPE.EVALUATE) public String ReadAsset(Context ctx, String id) { byte[] assetBytes = ctx.getStub().getState(id); if (assetBytes == null || assetBytes.length == 0) { throw new ChaincodeException( "Asset " + id + " does not exist" ); } return new String(assetBytes); } @Transaction(intent = Transaction.TYPE.SUBMIT) public String UpdateAsset(Context ctx, String id, String newOwner, int newValue) { String assetJson = ReadAsset(ctx, id); Asset asset = gson.fromJson(assetJson, Asset.class); asset.setOwner(newOwner); asset.setValue(newValue); asset.setUpdatedAt(Instant.now().toString()); String json = gson.toJson(asset); ctx.getStub().putStringState(id, json); return json; } @Transaction(intent = Transaction.TYPE.SUBMIT) public void DeleteAsset(Context ctx, String id) { byte[] existing = ctx.getStub().getState(id); if (existing == null || existing.length == 0) { throw new ChaincodeException( "Asset " + id + " does not exist" ); } ctx.getStub().delState(id); }}
Java's annotation system (@Transaction, @Default) explicitly declares transaction intent, which helps Fabric optimize query routing. The TYPE.EVALUATE intent tells the SDK that ReadAsset doesn't modify state, so it can skip the full endorsement cycle.
Testing chaincode before deployment catches bugs that would be expensive to fix on a live network. The Hyperledger Fabric Samples repository demonstrates testing patterns for all three languages. Unit tests validate business logic in isolation, while integration tests verify behavior against a running peer.
The fabric-contract-api-go library provides interfaces that are straightforward to mock. Use Go's built-in testing package with mock implementations of TransactionContextInterface and ChaincodeStubInterface:
// chaincode/smartcontract_test.gopackage chaincode_testimport ( "encoding/json" "testing" "github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-chaincode-go/shimtest" "github.com/your-org/asset-chaincode/chaincode")func TestCreateAsset(t *testing.T) { cc := new(chaincode.SmartContract) stub := shimtest.NewMockStub("asset", nil) // Verify asset doesn't exist before creation result := stub.GetState("asset1") if result != nil { t.Fatal("Expected nil state for non-existent asset") }}
For more sophisticated mocking, use the testify/mock package to simulate specific peer behaviors — network errors, concurrent writes, or endorsement failures.
Unit tests validate logic. Integration tests validate behavior against a real peer. Use Fabric's test network (fabric-samples/test-network) to deploy your chaincode and invoke transactions through the Fabric Gateway SDK. This catches issues that mocks miss — serialization bugs, endorsement policy mismatches, and state database quirks.
Testing chaincode before deployment is non-negotiable for production. Go uses shimtest.MockStub for unit tests, JavaScript uses Jest with mocked stubs, and Java uses JUnit with Mockito. The Hyperledger Fabric Samples repository provides reference testing patterns for all three languages.
Chaincode as a Service (CCaaS) is the modern deployment model for Fabric chaincode. Instead of the peer launching chaincode containers, the chaincode runs as an external service that the peer connects to. According to the Fabric 2.5 release notes, CCaaS is the recommended approach for production deployments because it gives operators full control over the chaincode runtime environment.
Traditional deployment packages chaincode into a Docker image that the peer launches and manages. This works for development but creates problems in production: you can't control the container runtime, resource limits, or networking. CCaaS flips the model. You deploy chaincode as a standalone service (container, process, or Kubernetes pod), and the peer connects to it via a configured endpoint.
Benefits of CCaaS:
Full control over the runtime environment and resource allocation
Chaincode survives peer restarts without re-instantiation
Standard deployment tooling (Kubernetes, Docker Compose, systemd)
Easier debugging — attach a debugger directly to the running process
Independent scaling of chaincode and peer infrastructure
Step 3: Package only the connection.json (not the chaincode binary) as the chaincode package:
tar cfz code.tar.gz connection.jsontar cfz asset-chaincode.tgz metadata.json code.tar.gz
Step 4: Install, approve, and commit as usual. The peer will connect to your external chaincode service instead of launching a container.
This is how I deploy all production chaincode now. It integrates cleanly with Kubernetes health checks, resource quotas, and rolling updates. The chaincode is just another microservice in your infrastructure.
Input validation is the most overlooked chaincode security practice. A Chainalysis 2024 report attributed $2.2 billion in losses to smart contract vulnerabilities and key compromises that year. While permissioned networks have a smaller attack surface than public chains, chaincode still needs defensive coding.
Validate every parameter before writing to the ledger. Never trust client input:
func (s *SmartContract) CreateAsset( ctx contractapi.TransactionContextInterface, id string, owner string, value int,) error { if len(id) == 0 || len(id) > 64 { return fmt.Errorf("asset ID must be 1-64 characters") } if len(owner) == 0 { return fmt.Errorf("owner cannot be empty") } if value < 0 { return fmt.Errorf("value must be non-negative") } // ... proceed with creation}
Non-deterministic code: Chaincode must produce identical results across endorsing peers. Avoid time.Now() for business logic (use transaction timestamp instead), random number generation, and external API calls that might return different results.
Range query phantoms: In CouchDB, range queries can return different results on different peers if writes happen between endorsement and commit. Use key-based lookups for endorsement-critical operations.
Unbounded iterations:GetStateByRange("", "") scans the entire state database. In production with millions of keys, this will time out. Always paginate.
Chaincode performance depends more on state access patterns than language speed. The Hyperledger Performance Whitepaper (2024) showed that optimized state access patterns can improve throughput by 3-5x regardless of language choice. The biggest gains come from reducing state reads per transaction and batching writes.
Pre-allocate slices when you know the expected size. Use json.RawMessage to avoid unnecessary marshal/unmarshal cycles for nested data. Compile with CGO_ENABLED=0 for a fully static binary that runs without libc dependencies.
Avoid deeply nested async/await chains. Use Buffer.from() instead of string concatenation for building state values. Consider using fabric-shim's streaming APIs for large result sets instead of loading everything into memory.
Use putStringState instead of putState with manual byte conversion. Configure JVM heap size appropriately in your Docker container — too small causes frequent garbage collection, too large wastes memory. Consider GraalVM native image compilation for near-instant cold starts.
Upgrading chaincode follows the same lifecycle as initial deployment — package, install, approve, commit — but with an incremented sequence number. The Fabric documentation specifies that all organizations must approve the new version before it becomes active. This multi-party approval process prevents any single org from pushing breaking changes.
Chaincode upgrades can read existing state but should handle backward compatibility. If you change the data model, include migration logic in an Init function or handle both old and new formats in your read operations. Never assume the state database only contains data in the new format — old entries persist.
Go delivers the highest throughput — approximately 15-30% faster than Java and 30-50% faster than JavaScript in benchmarks using identical business logic. The Hyperledger Performance Whitepaper (2024) measured these differences using standardized workloads. However, state access patterns matter more than language speed. A well-optimized JavaScript chaincode outperforms a poorly written Go one.
Yes. Each chaincode is an independent program, so different chaincodes on the same channel can use different languages. Organization A might deploy an asset-tracking chaincode in Go while Organization B deploys a compliance-reporting chaincode in Java. They coexist on the same channel without conflicts. The peer handles language-specific runtime containers independently.
For Go, use dlv (Delve debugger) with CCaaS mode. Run the chaincode as an external process and attach the debugger to the listening port. For JavaScript, use --inspect flag with Node.js and connect Chrome DevTools. For Java, use remote debugging with JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005. CCaaS mode makes all of these straightforward.
AI tools like Claude and Copilot can generate structurally correct chaincode in all three languages, but the output always needs human review. Common issues: missing input validation, non-deterministic code patterns (like using time.Now() for business logic), and incomplete error handling. Use AI to accelerate the first draft, then review for Fabric-specific correctness. Our guide on AI-assisted chaincode development covers the workflow in detail.
The default gRPC message size limit is 100 MB, configured via the peer's CORE_PEER_MAXRECVMSGSIZE and CORE_PEER_MAXSENDMSGSIZE environment variables. In practice, responses over a few megabytes indicate a design problem. Paginate large result sets using GetStateByRangeWithPagination or GetQueryResultWithPagination instead of returning everything at once.
LevelDB is the default and performs better for key-based lookups. CouchDB adds rich query support (JSON selector queries) but with additional operational complexity and slightly lower throughput. Use CouchDB if your chaincode needs to query by arbitrary fields. Stick with LevelDB if composite key lookups cover your query patterns — they almost always should.
Go remains the strongest default for Fabric chaincode in 2026. It delivers the best performance, produces the smallest binaries, and aligns with Fabric's own development ecosystem. But "best" is contextual. A team of JavaScript developers will ship faster in Node.js than struggling through Go's learning curve. An enterprise Java shop will integrate more smoothly with existing middleware using Java chaincode.
The patterns that matter across all languages are consistent: validate every input, test before deployment, use CCaaS for production, minimize state reads, and plan for upgrades from day one. The complete CRUD examples in this tutorial give you a working foundation — adapt the asset model to your domain, add your business rules, and build from there.
Start with a running Fabric network using our network creation guide, write your chaincode using the patterns above, and deploy it. For teams that want to accelerate the process, our guide on building a Fabric PoC with Claude Code shows how AI-assisted development fits into the workflow.
David Viejo is the founder of ChainLaunch and a Hyperledger Foundation contributor. He created the Bevel Operator Fabric project and has been building blockchain infrastructure tooling since 2020.
Free resource
Smart Contract Development Cheat Sheet — Go, JS, Java, and Solidity
Side-by-side code patterns for Fabric chaincode (Go/JS/Java) and Besu smart contracts (Solidity). Includes testing templates and common anti-patterns.