Kubernetes Operators extend the Kubernetes API to manage complex, stateful applications through Custom Resource Definitions (CRDs) and controller logic. Building operators from scratch requires deep knowledge of the Kubernetes API, controller patterns, and reconciliation loops. Operator frameworks abstract this complexity, providing scaffolding, testing utilities, and best-practice patterns. This guide compares three leading frameworks: Operator SDK, Kubebuilder, and Java Operator SDK.

What Are Kubernetes Operators?

Operators are software extensions to Kubernetes that use custom resources to manage applications and their components. They follow the core Kubernetes principle of declarative state management — you define the desired state in a YAML manifest, and the operator’s controller continuously works to reconcile the actual state with the desired state.

Common operator use cases include database provisioning, certificate management, service mesh configuration, and application lifecycle management. Rather than writing imperative scripts to set up complex infrastructure, operators let you declare what you want and handle the rest automatically.

Operator SDK — The Red Hat Standard

Operator SDK is the most widely used Kubernetes operator framework, maintained by the Operator Framework project (part of the Cloud Native Computing Foundation ecosystem). It provides a comprehensive toolkit for building, testing, and packaging operators in Go, Ansible, and Helm.

Key characteristics:

  • ⭐ 7,638 GitHub stars | Language: Go | Last updated: April 2026
  • Supports three development models: Go, Ansible, and Helm
  • Built on top of Kubebuilder for Go-based operators
  • Integrated with Operator Lifecycle Manager (OLM) for deployment
  • operator-sdk init scaffolds a complete project structure
  • Built-in testing with envtest (local Kubernetes API server)
  • Scorecard testing for operator quality validation
  • Bundle generation for OLM catalog distribution
  • Active Red Hat and community support

Operator SDK is the “batteries included” approach. It provides everything from project scaffolding to CI/CD integration, OLM packaging, and quality testing. If you’re building operators for production use and plan to distribute them through OperatorHub.io, this is the standard choice.

Operator SDK Installation and Project Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Install via Homebrew (macOS)
brew install operator-sdk

# Or download binary
curl -LO https://github.com/operator-framework/operator-sdk/releases/download/v1.39.0/operator-sdk_linux_amd64
chmod +x operator-sdk_linux_amd64
sudo mv operator-sdk_linux_amd64 /usr/local/bin/operator-sdk

# Initialize a new Go-based operator project
mkdir memcached-operator && cd memcached-operator
operator-sdk init --domain example.com --repo github.com/example/memcached-operator

# Create a new API and controller
operator-sdk create api --group cache --version v1 --kind Memcached --resource --controller

# Build and deploy to cluster
make docker-build docker-push IMG=example/memcached-operator:v0.0.1
make deploy IMG=example/memcached-operator:v0.0.1

Sample Operator Controller (Go)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// controllers/memcached_controller.go
package controllers

import (
    "context"
    "appsv1"
    "cachev1"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"
)

type MemcachedReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    // Fetch the Memcached instance
    memcached := &cachev1.Memcached{}
    err := r.Get(ctx, req.NamespacedName, memcached)
    if err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Check if the deployment already exists
    found := &appsv1.Deployment{}
    err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
    if err != nil && errors.IsNotFound(err) {
        // Define a new deployment
        dep := r.deploymentForMemcached(memcached)
        // Create the deployment
        return ctrl.Result{}, r.Create(ctx, dep)
    }

    return ctrl.Result{}, nil
}

func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&cachev1.Memcached{}).
        Owns(&appsv1.Deployment{}).
        Complete(r)
}

Kubebuilder — The Kubernetes SIG Project

Kubebuilder is the upstream Kubernetes project that provides the foundational SDK for building Kubernetes APIs using CRDs. Operator SDK’s Go model is built on top of Kubebuilder, meaning Kubebuilder provides the core abstractions that both frameworks share.

Key characteristics:

  • ⭐ 9,115 GitHub stars | Language: Go | Last updated: April 2026
  • Official Kubernetes SIG project (sigs.k8s.io)
  • Core SDK that Operator SDK’s Go model builds upon
  • Focused on Go-based operator development only
  • Direct access to controller-runtime library patterns
  • Less abstraction than Operator SDK — closer to the metal
  • kubebuilder init creates minimal project scaffolding
  • No OLM integration out of the box
  • Preferred by teams building operators for internal use

Kubebuilder is the “do it yourself” option. It provides the essential building blocks without the extra layers of abstraction that Operator SDK adds. If you want maximum control and don’t need OLM integration, Kubebuilder gives you a cleaner, more direct path.

Kubebuilder Installation and Project Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Install kubebuilder
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

# Initialize project
mkdir my-operator && cd my-operator
go mod init my-operator

kubebuilder init --domain my.domain --repo my.domain/my-operator

# Create API with CRD and controller
kubebuilder create api --group webapp --version v1 --kind Guestbook --resource --controller

# Generate manifests and run locally
make manifests
make run

# Build and deploy
make docker-build docker-push IMG=my-operator:v0.0.1
make deploy IMG=my-operator:v0.0.1

Sample Controller Reconciliation Logic

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// controllers/guestbook_controller.go
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    var guestbook webappv1.Guestbook
    if err := r.Get(ctx, req.NamespacedName, &guestbook); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Reconcile deployment state
    deployment := &appsv1.Deployment{}
    err := r.Get(ctx, types.NamespacedName{
        Name:      guestbook.Name + "-deployment",
        Namespace: guestbook.Namespace,
    }, deployment)

    if apierrors.IsNotFound(err) {
        deployment = r.newDeploymentForGuestbook(&guestbook)
        if err := r.Create(ctx, deployment); err != nil {
            log.Error(err, "Failed to create Deployment")
            return ctrl.Result{}, err
        }
        // Set owner reference for garbage collection
        if err := ctrl.SetControllerReference(&guestbook, deployment, r.Scheme); err != nil {
            return ctrl.Result{}, err
        }
    }

    return ctrl.Result{}, nil
}

Java Operator SDK — Operators for the JVM Ecosystem

Java Operator SDK brings the Kubernetes operator pattern to Java developers. Built on top of the Fabric8 Kubernetes client, it provides annotations-driven controller development familiar to Spring Boot and enterprise Java developers.

Key characteristics:

  • ⭐ 928 GitHub stars | Language: Java | Last updated: April 2026
  • Built for Java developers — no Go knowledge required
  • Annotation-based controller definition
  • Integrates with Spring Boot ecosystem
  • Uses Fabric8 Kubernetes client for API access
  • Supports both reactive and blocking programming models
  • Built-in retry, event handling, and status management
  • Quarkus and Spring Boot starters available
  • Best for Java-heavy organizations

The Java Operator SDK is the right choice when your team’s expertise is primarily in Java. It lets you write operators using familiar patterns — dependency injection, annotations, and the Spring/Quarkus ecosystem — rather than learning Go and the controller-runtime library.

Java Operator SDK Setup with Spring Boot

1
2
3
4
5
6
<!-- pom.xml -->
<dependency>
    <groupId>io.javaoperatorsdk</groupId>
    <artifactId>operator-framework-spring-boot-starter</artifactId>
    <version>4.9.6</version>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// MyOperator.java
package com.example.operator;

import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.springboot.starter.OperatorStarter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyOperatorApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyOperatorApplication.class, args);
    }
}

// Reconciler implementation
@ControllerConfiguration
public class MyResourceReconciler implements Reconciler<MyResource> {

    @Override
    public UpdateControl<MyResource> reconcile(MyResource resource, Context<MyResource> context) {
        // Reconcile the desired state
        String desiredState = resource.getSpec().getDesiredState();
        String currentState = resource.getStatus().getCurrentState();

        if (!desiredState.equals(currentState)) {
            // Perform reconciliation
            resource.getStatus().setCurrentState(desiredState);
            return UpdateControl.updateStatus(resource);
        }

        return UpdateControl.noUpdate();
    }
}

Java Operator SDK with Quarkus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Quarkus-based operator
import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
import io.quarkiverse.operatorsdk.annotations.RBACRule;
import io.javaoperatorsdk.operator.api.reconciler.*;

@ControllerConfiguration(
    name = "my-reconciler",
    generationAwareEventProcessing = true
)
@CSVMetadata(
    name = "my-operator",
    version = "0.1.0"
)
@RBACRule(
    apiGroups = {"my.domain"},
    resources = {"myresources"},
    verbs = {"get", "list", "watch", "update", "patch"}
)
public class QuarkusReconciler implements Reconciler<MyResource> {

    @Override
    public UpdateControl<MyResource> reconcile(MyResource primary, Context<MyResource> context) {
        // Business logic for reconciliation
        return UpdateControl.patchStatus(primary);
    }
}

Comparison Table

FeatureOperator SDKKubebuilderJava Operator SDK
GitHub Stars7,6389,115928
Primary LanguageGoGoJava
Maintained ByOperator FrameworkKubernetes SIGCommunity
Languages SupportedGo, Ansible, HelmGo onlyJava only
Built OnKubebuilder (Go model)controller-runtimeFabric8 client
OLM IntegrationNativeManual setupLimited
Scaffolding CLIoperator-sdkkubebuilderMaven/Gradle plugin
Testing Frameworkenvtest + scorecardenvtestJUnit + test containers
Spring Boot SupportNoNoYes
Quarkus SupportNoNoYes
Ansible OperatorsYesNoNo
Helm OperatorsYesNoNo
Bundle GenerationBuilt-inManualManual
OperatorHub ReadyYesManualManual
Learning CurveMediumMedium-HighLow (for Java devs)
Best ForProduction operators, OLM distributionInternal operators, Go teamsJava-heavy organizations

When to Use Each Framework

Choose Operator SDK when:

  • You plan to publish your operator to OperatorHub.io or Artifact Hub
  • You need OLM (Operator Lifecycle Manager) integration for version management
  • You want to write operators in Ansible or Helm (not just Go)
  • You need built-in scorecard testing for operator quality validation
  • You want the most comprehensive tooling with the largest community
  • Your organization standardizes on Red Hat’s operator ecosystem

Choose Kubebuilder when:

  • You want minimal abstraction and direct access to controller-runtime patterns
  • You’re building internal operators that don’t need OLM packaging
  • You prefer to assemble your own tooling chain rather than use a bundled solution
  • You want to stay close to the upstream Kubernetes SIG project
  • Your team is comfortable with Go and the controller-runtime library
  • You need fine-grained control over every aspect of the operator

Choose Java Operator SDK when:

  • Your team’s primary expertise is in Java, not Go
  • You want to leverage existing Spring Boot or Quarkus knowledge
  • Your organization has a mature Java ecosystem and CI/CD pipeline
  • You prefer annotation-driven development over code generation
  • You need integration with Java testing frameworks (JUnit, TestContainers)
  • You want to build operators alongside existing Java microservices

Why Build Kubernetes Operators?

Operators bring significant operational advantages for teams managing complex applications on Kubernetes:

Automated lifecycle management: Operators handle installation, upgrades, backups, and recovery automatically. Instead of manually running scripts to upgrade a database, the operator reads the desired version from a CRD and performs the upgrade with zero downtime.

Declarative infrastructure: Just like Kubernetes Deployments manage pod state, operators manage application state. You declare what you want in YAML, and the operator’s reconciliation loop ensures the cluster matches that state — including handling failures, scaling, and configuration drift.

Domain-specific automation: Operators encode operational expertise. A database operator knows how to perform safe schema migrations, while a caching operator understands cache warming patterns. This knowledge becomes reusable infrastructure code.

Consistent operations across clusters: Once you build and package an operator, it works the same way across development, staging, and production clusters. OLM handles version pinning, dependency resolution, and rollout strategies.

For teams deploying operators at scale, our Kubernetes management guide covers cluster orchestration platforms. If you manage secrets for your operators, our Kubernetes secrets management comparison covers the leading solutions. For policy enforcement alongside operators, check our policy enforcement guide.

FAQ

What’s the difference between Operator SDK and Kubebuilder?

Operator SDK is a higher-level framework that wraps Kubebuilder for its Go-based operators. Kubebuilder is the upstream Kubernetes SIG project that provides the core controller-runtime SDK. If you use Operator SDK’s Go model, you’re indirectly using Kubebuilder. The key difference: Operator SDK adds OLM integration, Ansible/Helm operator support, scorecard testing, and bundle generation. Kubebuilder is leaner and closer to the metal.

Can I migrate from Kubebuilder to Operator SDK?

Yes. Since Operator SDK’s Go model is built on Kubebuilder, the project structure and controller code are largely compatible. You can initialize an Operator SDK project and copy your Kubebuilder controllers into it. The main changes involve updating the Makefile and adding Operator SDK-specific configurations for OLM integration.

Is the Java Operator SDK production-ready?

Yes. The Java Operator SDK is used in production by several organizations, particularly those with Java-centric infrastructure. It’s actively maintained with regular releases. However, the ecosystem is smaller than Operator SDK’s — fewer examples, fewer community resources, and no direct OperatorHub.io integration. For Java teams, the productivity gains from using familiar tools often outweigh these limitations.

Do I need OLM to deploy an operator?

No. OLM (Operator Lifecycle Manager) is optional. You can deploy operators as standard Kubernetes Deployments with RBAC rules and CRDs. OLM adds value for version management, dependency resolution, and catalog-based distribution — important for multi-tenant clusters or when publishing operators publicly, but not required for internal use.

Can I write an operator in Python or other languages?

The three frameworks covered here support Go, Ansible, Helm, and Java. For Python, you can use the kopf framework, which provides similar annotation-based operator development. Other options include the Kubernetes client libraries for any language — you’d implement the reconciliation loop manually without framework scaffolding.

How do operators handle reconciliation failures?

All three frameworks use the controller-runtime pattern: when reconciliation fails, the controller requeues the request with exponential backoff. The operator retries automatically, and you can configure maximum retry counts and backoff intervals. Status conditions on the custom resource communicate the current reconciliation state to users and other controllers.