Introduction
Every API, every form submission, every configuration file — they all need validation. In Go, where the language itself provides no built-in validation annotations, the ecosystem has evolved three distinct approaches: go-playground/validator (struct tag-based, declarative), ozzo-validation (programmatic, composable), and govalidator (standalone functions, zero-struct approach).
Choosing the right validation strategy affects not just your code’s correctness but also its readability and testability. This guide compares all three libraries with production-ready code examples.
Library Overview
| Feature | go-playground/validator | ozzo-validation | govalidator |
|---|---|---|---|
| GitHub Stars | 20,024 | 4,103 | 6,203 |
| Approach | Struct tags (declarative) | Programmatic rules (composable) | Standalone functions |
| Type Safety | Runtime (struct tags) | Compile-time (typed rules) | Runtime (string assertions) |
| Custom Validators | Register tag functions | Custom Rule interface | Wrap functions |
| i18n Support | Translation package | Manual via error messages | N/A |
| Struct-Level Validation | Yes (cross-field) | Yes (composable) | No |
| Performance | Excellent (cached reflection) | Good | Moderate |
| Active Maintained | Yes (committed June 2026) | Moderate (last 2024) | Yes (committed June 2026) |
go-playground/validator: Declarative Tag-Based
go-playground/validator is the de facto standard for Go struct validation with over 20,000 stars. It uses struct tags — a familiar Go pattern also used by encoding/json and database/sql.
Installation:
| |
Basic Usage:
| |
Custom Validation:
| |
Key advantages:
- Familiar Go idiom: Struct tags are well-understood by every Go developer
- Rich built-in tags: 50+ validators for strings, numbers, time, network, and more
- Internationalization: Separate
translationspackage with 15+ languages - Dive for slices:
divetag validates each element of slices and maps
ozzo-validation: Programmatic Composable Rules
ozzo-validation takes an object-oriented approach. Instead of struct tags, you build validation rules programmatically by chaining Rule implementations. This is more verbose but gives you full compile-time type checking.
Installation:
| |
Basic Usage:
| |
Custom Rules:
| |
Key advantages:
- Type-safe: No string-based tag parsing — all rules are Go functions
- Composable: Chain rules, nest struct validation, combine with
When() - IDE-friendly: Autocomplete works on all validation methods
- Explicit error types: Custom error codes with structured error messages
govalidator: Standalone String-Based Functions
govalidator takes the simplest approach: it provides a rich set of standalone validation functions plus optional struct tag support. It is ideal when you need quick, one-off validations without building a validation framework.
Installation:
| |
Basic Usage:
| |
Key advantages:
- Zero ceremony: Call
govalidator.IsEmail(str)anywhere without struct setup - 70+ built-in validators: Credit cards, IP ranges, MAC addresses, Base64, DNS names, etc.
- Custom validators: Register
govalidator.TagMap["my-tag"] = govalidator.Validator(myFunc) - Low overhead: No interface implementations required
Key disadvantages:
- String-based tags: Errors reference tag strings, not Go fields — harder to debug in large structs
- No composability: Cannot chain or conditionally apply validators programmatically
- Limited cross-field: No built-in cross-field validation like
gtfield
When to Use Each Library
go-playground/validator is the right choice for most Go APIs. Its struct tag approach mirrors the patterns developers already know from json and db tags. It scales well to hundreds of request types because validators are declared alongside the struct fields they validate.
ozzo-validation shines in complex business logic where validation rules depend on runtime state. Its programmatic API makes conditional validation explicit and testable. If you need to validate deeply nested struct hierarchies with branching logic, ozzo’s composability pays off.
govalidator is perfect for simple projects, CLI tools, and cases where you primarily need standalone validation functions. If you find yourself writing if !govalidator.IsURL(url) { ... } scattered through your codebase, it is the lightest-weight solution.
Integration with Web Frameworks
All three libraries integrate with popular Go web frameworks:
| |
For broader data validation, see our JSON Schema Validation guide. If you need validation at the schema governance level, check our API Schema Validation guide. For Kubernetes-specific validation patterns, our K8s YAML Validation guide covers admission policies.
Validation Performance Considerations
When choosing a validation library, raw throughput is only one factor. Here is what to consider for production Go services:
Memory allocation: go-playground/validator caches reflection metadata after the first validation call on a given struct type, so subsequent validations allocate zero heap — critical for high-throughput APIs handling thousands of requests per second. ozzo-validation allocates a new error container per Validate() call, adding roughly 200 bytes of heap per validation. govalidator’s standalone functions are allocation-light, but its struct tag mode uses reflection without caching.
Startup time: go-playground/validator scans struct tags at first validation call, adding approximately 2ms of init time per unique struct type. This is negligible for long-running servers but matters for CLI tools that validate once and exit. govalidator has near-zero startup cost for standalone function calls.
Error message quality: ozzo-validation produces the most developer-friendly error messages because rules are Go code with explicit error strings. go-playground/validator’s tag-based errors (e.g., “failed on ‘gte’ tag”) require translation for end-user consumption. govalidator returns flat error strings that are straightforward but lack field-path context.
Concurrent safety: go-playground/validator is fully goroutine-safe — its cached reflection metadata is immutable after initialization. ozzo-validation’s Validate() method is safe for concurrent use since validation is a read-only operation on the struct. govalidator’s standalone functions are inherently concurrent-safe.
For most production Go services, the allocation-free nature of go-playground/validator after initial warmup makes it the best choice for sustained throughput. If you are building a CLI tool that validates once at startup, govalidator’s simplicity and zero-setup experience wins.
FAQ
Which Go validation library is fastest?
go-playground/validator is the fastest for struct validation because it caches reflection metadata after the first call. For a 10-field struct, it processes ~2 million validations per second. ozzo-validation adds ~15% overhead from interface dispatch. govalidator is fastest for isolated function calls like IsEmail() since it bypasses struct reflection entirely.
Can I use multiple validation libraries together?
Yes. Many projects use govalidator for quick standalone checks (URL, email) and go-playground/validator for request struct validation in HTTP handlers. They do not conflict because they operate at different abstraction levels.
How do I return structured validation errors to API clients?
All three libraries support custom error formatting. With go-playground/validator, you can extract ValidationErrors and map each to a JSON response with field path, failed tag, and translated message. ozzo-validation returns structured validation.Errors natively. govalidator returns a flat string or Errors slice.
What about validation in Go protobuf/gRPC services?
For gRPC, use protovalidate (Buf’s next-gen protobuf validation) or protoc-gen-validate which generate Go validation code from .proto constraint annotations. These integrate with gRPC interceptors for automatic request validation before your handler executes.
Does go-playground/validator support i18n?
Yes. The github.com/go-playground/validator/v10/translations package provides translation files for Chinese, Japanese, Korean, French, German, Spanish, Portuguese, Russian, Turkish, Indonesian, Vietnamese, and more. You register a translator and use err.Translate(trans) to get localized error messages.
Can I validate deeply nested structs with tags?
Yes. go-playground/validator automatically recurses into nested structs. For slices of structs, use the dive tag: validate:"required,dive". ozzo-validation handles nesting via validation.Field(&s.Nested) where Nested implements the Validatable interface.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com