Comparison
Before adopting Ihawu, it is worth knowing what you already have. Jackson ships two features that
solve part of this problem, and your database probably ships a third. Ihawu’s Jackson backend is built
on Jackson’s serializer SPI — it is not an alternative to Jackson (a kotlinx.serialization backend
also ships) — and for a small enough problem it is not necessary at all.
@JsonView
Section titled “@JsonView”Annotate properties with view marker classes and pick a view per request.
@JsonView(Views.Internal::class)val socialSecurityNumber: String?Use it when you have a handful of fixed response shapes, known at compile time, in a single service. It is free, it is built in, and for two or three shapes it is the right tool. Reaching for Ihawu here is overkill.
Where it runs out:
- It can only include or exclude. There is no way to substitute a value, so a redacted
***-**-****is not expressible — you can drop the SSN, but you cannot mask it. - Views are compile-time classes. Adding a role means writing a class and recompiling. With roles on one axis and sensitivity on another, view classes multiply.
- The rules live in annotations on the type, so a policy change is a code change.
@JsonFilter
Section titled “@JsonFilter”Annotate the type with a filter id and supply a FilterProvider at write time.
val filters = SimpleFilterProvider() .addFilter("employee", SimpleBeanPropertyFilter.serializeAllExcept("socialSecurityNumber"))mapper.writer(filters).writeValueAsString(employee)Dynamic, and much closer to what Ihawu does. Read honestly, ihawu-core is a policy-driven,
principal-aware @JsonFilter with the wiring and the policy model supplied for you.
Use it when one or two endpoints need runtime-varying exclusion. SimpleBeanPropertyFilter gets
you there in about twenty lines.
Where it runs out:
- Redaction means writing the engine yourself.
SimpleBeanPropertyFilterincludes and excludes; substituting a placeholder requires subclassingPropertyFilterand overridingserializeAsField. That is precisely the code Ihawu is. - Every call site has to be wired. In Spring that means wrapping each response in a
MappingJacksonValueand attaching the provider. Miss one handler and it is unmasked. - There is no policy model. You write imperative code to decide the filter set for each caller; there is nothing to load from a database, a config file, or OPA.
- Nesting is manual. A filter id has to be attached to each type you want filtered, and the filter set assembled for the whole graph.
What Ihawu adds
Section titled “What Ihawu adds”Given a @JsonFilter can be bent into doing most of this, the value is in what you do not have to
build:
@JsonView |
@JsonFilter |
Ihawu | |
|---|---|---|---|
| Drop a field | ✅ | ✅ | ✅ HIDE |
| Replace with a placeholder | ❌ | Custom PropertyFilter |
✅ REDACT |
| Rules outside code (config, DB, OPA) | ❌ | ❌ | ✅ ResourcePolicyResolver SPI |
| Add a role without recompiling | ❌ | ❌ | ✅ |
| Wiring per call site | Per handler | Per handler | ✅ None — register the module once |
| Nested types and collections | Manual | Manual | ✅ Automatic |
| Behaviour when identity is missing | Serializes | Serializes | ✅ Fails closed — emits {} |
Two of those are worth drawing out.
Fail-closed on missing identity. If no IhawuPrincipal is attached to the write, Ihawu masks
every field of the resource rather than serializing it. A forgotten wiring step produces an empty
object, not a leak. With @JsonView or a hand-rolled filter, the failure mode of forgetting runs the
other way.
No per-call-site wiring. IhawuModule is registered once on the ObjectMapper; the Spring Boot
starter does even that for you. Controllers return their domain objects and there is nothing to
remember at each handler — which matters, because the handler you forget is the one that leaks.
Masking in the database
Section titled “Masking in the database”Column-level GRANTs, PostgreSQL row-level security, Oracle Data Redaction, and Snowflake dynamic
data masking all mask at the source. Where they apply they are stronger than Ihawu: the sensitive
value never enters your JVM at all, so it cannot reach a log, a cache, or an event stream. If your
requirement is that an application must never hold a value, enforce it there, not here.
Ihawu is complementary, and earns its place where source-level masking cannot help: when the application legitimately needs the full value — to compute payroll, run a fraud check, populate an audit trail — but must not return it to this particular caller. The database cannot make that distinction, because from its side both reads look identical. Ihawu is the last-mile enforcement point for exactly that case.
For the sinks Ihawu does not cover, see How Ihawu Works.