sensitive: annotation-driven log masking for Java, without regex backtracking
🔐Sensitive log tool for java, based on java annotation. (基于注解的 java 日志脱敏工具框架,更加优雅的日志打印。支持自定义哈希、支持基于 log4j2 插件的统一脱敏、支持 logback 插件统一脱敏)
At a glance
- What is it?
- The sensitive project masks sensitive fields in Java logs by putting an annotation on each field, and it can also mask whole log events through logback and log4j2 plugins that match with a deterministic automaton rather than regular expressions. It suits financial and Chinese-market backends where regulations require user names, phone numbers, identity numbers and bank cards to be masked in logs.
- Who is it for?
- sensitive fits a Java team whose logs must not contain personal or financial data and who want masking declared next to the field rather than applied by hand at each call site, and it does not fit a project that only needs to redact a handful of values by hand.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 84 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why log masking is a compliance problem, not a style choice
The project opens by framing log masking as a common security requirement, and then notes that the ordinary approach, calling a utility method, intrudes too much on the code and is particularly troublesome to write. The alternative it offers is an annotation-based approach with the common masking strategies built in, so the developer does not have to assemble them.
The reason the requirement exists in this form is regulatory, and the readme lists the fields that national rules require to be masked for the security of financial transactions: the user name, the phone number, the email address, the bank card number, the password and the identity document number.
That list explains the shape of the library. Masking a phone number and masking an identity document number are different problems, and neither is a regular expression you would want to write twice, in every service, for every log statement that happens to touch a user object. A shared library with a strategy per field type removes the repetition and, more importantly, removes the chance that one service implements it differently from the next.
A separate section covers encryption at rest, where the same set of values needs encrypting when stored, with passwords irreversibly encrypted and the others reversibly. The readme is explicit that similar functionality exists elsewhere and that persistence encryption is not in the scope of this system. That boundary is worth noting early, because a reader looking for a full data-protection library will not find one here.
How the masking works: a deterministic automaton, not a regex
The technical claim in the readme is the interesting one, and it is stated in the context of the logging plugins: the plugin approach solves the backtracking problem that regex matching can hit on long text, and its performance is far better than regex.
The repository topics include dfa, which is the abbreviation for deterministic finite automaton, and that is the mechanism. A regex engine works by backtracking: when a pattern fails to match at one position, it retries alternatives, and on a long string with a pathological pattern that can blow up combinatorially. A DFA matching approach instead processes the text once, moving through states, so the work is proportional to the length of the input and cannot backtrack. For a log pipeline where every line of every request is scanned on the way out, that difference is the difference between a masking layer you can leave enabled and one you cannot.
The plugin modules in the repository tree are sensitive-log4j2 and sensitive-logback, with the logback plugin added in v1.6.0, and they are described as unified masking strategies at the plugin level. A unified strategy means you configure masking once, for the logger, rather than per field per class, and every message that goes through that logger gets the same treatment regardless of which code produced it.
The field-level annotation approach and the plugin approach are therefore complementary rather than competing. The annotations cover the structured case where you know which fields are sensitive, and the plugins cover the unstructured case where a sensitive value may appear in a free-form message that nobody tagged.
Declaring the strategy on the field, with a dozen built-in options
The mechanism for structured data is an annotation per field, and the readme walks through a bean with one strategy annotation on each property. The class below is the example the documentation uses, with the annotations in the order the readme lists them:
UserAnnotationBean bean = new UserAnnotationBean();
bean.setUsername("张三");
bean.setPassword("123456");
bean.setPassport("CN1234567");
bean.setPhone("13066668888");
bean.setAddress("中国上海市浦东新区外滩18号");
bean.setEmail("[email protected]");
bean.setBirthday("20220831");
bean.setGps("66.888888");
bean.setIp("127.0.0.1");
bean.setMaskAll("可恶啊我会被全部掩盖");
bean.setMaskHalf("还好我只会被掩盖一半");
bean.setMaskRange("我比较灵活指定掩盖范围");
bean.setBandCardId("666123456789066");
bean.setIdNo("360123202306018888");Two things are worth noting in that list. First, the strategy set covers the regulatory fields and then some, with address, birthday, GPS coordinates and IP address added on top, plus three generic masking strategies that are not tied to a data type: mask everything, mask half, and mask an arbitrary range. That last one is what you reach for when a field is sensitive but its format is not one the library knows.
Second, the readme's feature list states that the custom strategy support includes the condition under which a strategy takes effect, so a strategy can be conditional rather than unconditional, and that users can define their own annotations. A custom annotation is the extension point for a data type the library has never heard of.
The four core API calls, and the deep copy requirement
The utility class SensitiveUtil exposes a small set of entry points, and the readme tabulates them by argument and result type.
desCopy takes a target object and returns a deep-copied masked object. desJson takes a target object and returns the masked object as json, and is described as having better performance. Then there are the collection variants, desCopyCollection for a collection of target objects and desJsonCollection for a collection of masked json objects.
The deep copy is a real constraint on your design, and the feature list is explicit about it: the project does a Java deep copy, and the original object does not have to implement any interface. That last clause matters more than it first appears. The usual way to make a masking copy is to clone the object or serialize it, which normally requires the object to be cloneable or serializable. Requiring neither means a plain POJO with plain fields and ordinary getters can be masked without being made to cooperate.
The v1.9.1 fix notes are about this copy path. They add an optional deep copy cycle detection, off by default, which avoids overflowing the recursion depth on cyclic references, and they fix null pointer exceptions from nested empty objects, empty array elements and a FastJSON2 context with no fields, plus a data and type problem caused by replacing the collection implementation when masking collections of primitive types.
The FastJSON2 path is the one that generates json directly from a masked object rather than copying first, which is why it is described as the better performing option, and it has its own module with its own bug fixes.
Hashing masked values so logs stay useful for debugging
There is a problem with masking that the project addresses directly, and it appears in its own article series as well as in the feature list: after masking, how do you locate a problem in the logs if every value has become stars.
The answer is the custom hash strategy, described in the readme as making it more convenient to locate log problems. Instead of replacing a value with asterisks, the masked output can carry a short hash of the original, so two occurrences of the same sensitive value produce the same token and you can still correlate them.
The v1.8.0 release notes show how the configuration grew around this. A showHash option was added to control whether the hash value is displayed, a global configuration class SensitiveConfig was introduced to centralise configuration, and a sensitive.properties file was added so that configuration can live outside code. The release also states that multiple configuration mechanisms are supported: programmatic configuration, system properties, and a configuration file.
That combination is what makes the library usable in a real deployment. The strategy of what gets masked is a code-level concern, since it is attached to fields. Whether a hash is shown, and where the configuration comes from, is an operational concern, and the readme moves those to properties and system properties so a security team can change them without a rebuild.
Installing the core module and calling it from a Maven project
The environment requirement is JDK 1.8 or later with Maven 3.x, and the module you depend on for the core API is sensitive-core:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-core</artifactId>
<version>1.9.1</version>
</dependency>The build script in the repository is Maven, and the modules are split by concern: sensitive-annotation holds the annotation definitions and the strategies, sensitive-api is the public surface, sensitive-core is the implementation, sensitive-log4j2 and sensitive-logback are the two logging plugin modules, and sensitive-test and sensitive-test2 hold the test projects.
The example in the readme builds a bean, fills it with values, and then compares the masked output against the expected output, which is the shape of the test you would write for your own fields:
UserAnnotationBean bean = new UserAnnotationBean();
bean.setUsername("张三");
bean.setPassword("123456");
bean.setPassport("CN1234567");
bean.setPhone("13066668888");
bean.setAddress("中国上海市浦东新区外滩18号");
bean.setEmail("[email protected]");
bean.setBirthday("20220831");
bean.setGps("66.888888");
bean.setIp("127.0.0.1");
bean.setMaskAll("可恶啊我会被全部掩盖");
bean.setMaskHalf("还好我只会被掩盖一半");
bean.setMaskRange("我比较灵活指定掩盖范围");
bean.setBandCardId("666123456789066");
bean.setIdNo("360123202306018888");The expected outputs in the readme's test block show what each strategy produces, and they are worth reading as the specification of the built-in behaviour: a Chinese name keeps its first character and masks the rest, a passport number keeps its prefix and last two digits, a phone number keeps its first four and last three, an email keeps the local part's first two characters and the domain's extension, a password is replaced entirely with null, and the mask-half strategy keeps the first part of the string.
The wider toolbox around it, and what the licence file says
The readme closes with a table of related libraries by the same author, and it is a reasonable map of the surrounding problem space: sensitive-word, a high performance sensitive word core library, and sensitive-word-admin, a sensitive word console with a separated front end and back end; sensitive itself, described as a high performance log masking component; auto-log, a unified logging aspect component with full link traceId support; and a set of encryption components, encryption-local for an offline encryption machine, encryption for a standard encryption machine API plus local client, and encryption-server for the encryption machine service.
Those last three are the answer to the gap the readme flagged earlier, where persistence encryption was declared out of scope for this project. The same author provides the encryption side separately, which is worth knowing because the split between masking at log time and encryption at rest is a real architectural decision rather than an omission.
On maintenance, the repository is not archived and the last push was on 2026-08-01. There are no GitHub releases, and the version history lives in CHANGE_LOG.md, which the readme links, with the v1.9.1 fixes, the v1.8.0 features and the v1.6.0 addition of the logback plugin all described in the readme itself.
The licence needs one note of caution. GitHub's licence field for it is a custom one it cannot classify, and there is a LICENSE.txt in the repository root that the Apache 2.0 badge points at. The badge is not the licence text, and the file is what to read before shipping this in a product. The repository also has a design-docs/ directory, a doc/ directory and HOW_TO_DEPLOY_NEW.md, which suggests the project keeps design and deployment notes in the tree rather than only on a site.
Editorial conclusion
sensitive fits a Java team whose logs must not contain personal or financial data and who want masking declared next to the field rather than applied by hand at each call site, and it does not fit a project that only needs to redact a handful of values by hand. Before adopting it, read the release notes in CHANGE_LOG.md for the version you pin, decide whether the core dependency or the logback and log4j2 plugin modules are the ones you need, and check the licence file in the repository, because the licence listing in the project data is a custom one that GitHub cannot classify.
Frequently asked questions
What does the sensitive Java library do?
It masks sensitive fields in log output using annotations placed on the fields, with built-in strategies for user name, phone number, email, bank card, password and identity document number, plus address, birthday, GPS, IP and generic mask-all, mask-half and mask-range strategies.
How is sensitive different from a regular expression approach?
The log4j2 and logback plugins mask at the plugin level using deterministic matching, which the readme says avoids the backtracking problem regex matching can hit on long text and performs far better than regex. The repository topics include dfa, which points at the automaton-based mechanism.
How do I add sensitive to a Maven project?
The readme asks for JDK 1.8 or later and Maven 3.x, then a dependency on com.github.houbb:sensitive-core at version 1.9.1. The logback and log4j2 masking plugins live in separate modules, sensitive-logback and sensitive-log4j2, and the logback plugin was added in v1.6.0.
What are the main SensitiveUtil methods?
desCopy returns a deep-copied masked object, desJson returns the masked object as json and is described as performing better, desCopyCollection masks a collection of objects, and desJsonCollection masks a collection into json. The deep copy requires the original object to implement no interface.
Can sensitive hash masked values so logs stay searchable?
Yes, through a custom hash strategy described as making it easier to locate log problems. The v1.8.0 release added a showHash option, a global SensitiveConfig class, a sensitive.properties file, and support for programmatic configuration, system properties and configuration files.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/houbb-sensitive)Was this useful?