Custom PETs User Guide
A custom PET (Privacy Enhancing Technology) lets you run Java logic as part of a policy. Use it when the standard PETs cannot express the required transformation. The custom PET transforms one value at a time and returns the replacement value; it does not change the job type, policy configuration, or access decision.
The Custom PET UI or its management API is responsible for creating and maintaining the PET definition. This guide covers the Java code you supply and the eXate services available from that code.
Java code structure
Provide a public static method that accepts one String and returns a
String. Its configured name must exactly match the method name.
public class DynamicClass {
public static String applyMask(String value) {
if (value == null) {
return null;
}
logger.info("CUSTOM_PET action=applyMask inputLength=" + value.length());
return value.replaceAll("(?s).", "*");
}
}
DynamicClass may be omitted when the UI accepts a method-only definition: the
platform then adds the class wrapper. If you provide a class yourself, its name
must be DynamicClass.
The platform adds imports for the common eXate, JSON, and job-configuration classes. You may add ordinary Java imports for other classes available on the runtime classpath.
Context available in custom code
The following static values are supplied inside DynamicClass:
| Value | Use |
|---|---|
logger |
Write platform logs through eXate LoggingUtil. |
jobConfig |
Read the current job context. Do not log the whole object. |
attributeCode |
The attribute currently being processed. |
exate |
Use the current job's ExateService for policy-driven processing. |
Use attributeCode unless your logic intentionally processes a different,
configured attribute. A custom PET is normally associated with the CUSTOM
attribute; installations can configure a different custom attribute code.
exate functions
exate runs in the same job context as the custom PET. Its result therefore
depends on the current job type and the configured policies. The table covers
the public processing functions available on the injected service.
| Function | Purpose and use |
|---|---|
process(value, attribute, subPetId, dataSubjectUserName) |
Apply the configured policy flow to value. Select a PET by numeric ID. Use 0 only when default PET selection is intended. Pass "" when no data-subject context is needed. |
process(value, attribute, subPetName, dataSubjectUserName) |
As above, but select a PET by its exact display name. If no matching name is found, eXate falls back to ID 0; prefer the ID overload when predictable selection is essential. |
processStatusFn(attribute, subPetId, dataSubjectUserName) |
Returns a Scala function that reports access/processing status for a value. It is supported only for Reconstruct and Restrict jobs. From Java, invoke the returned function's apply(value) method and treat the result as a Boolean. |
processFn(attribute, subPetId, dataSubjectUserName) |
Returns the underlying Scala processing function. This is useful only for advanced Java/Scala integrations that need to reuse it across many values; call apply(value) on the result. For normal custom PET code, call process directly. |
processFPE(value, attribute, alphabet, paddingValue) |
Apply inline string format-preserving encryption (FPE) with the supplied alphabet and padding character/value. It encrypts for Pseudonymise and Data masking, decrypts for Reconstruct, and is not available for Restrict. |
getDeterministicIndex(value, attribute, maxVal) |
Returns a repeatable zero-based integer derived from the value, attribute, job snapshot, tenant, and third-party context. maxVal must be greater than zero. |
postProcess() |
Persist shard information created by nested pseudonymisation. Call it after nested process calls that pseudonymise data, before returning from the custom PET. |
process
Use process to delegate to a PET already configured in eXate instead of
duplicating its implementation:
public static String applyConfiguredPet(String value) {
logger.debug("CUSTOM_PET action=applyConfiguredPet attribute=" + attributeCode);
String result = exate.process(value, attributeCode, 0, "");
exate.postProcess();
return result;
}
The current job type determines the operation: data masking masks, pseudonymise
protects, reconstruct reconstructs subject to access controls, and restrict
returns the original or restricted value according to access. Calling process
does not bypass access controls.
processFPE
Use processFPE when your custom PET needs FPE with an alphabet chosen by the
code rather than an FPE PET selected from policy configuration. The alphabet
must contain the characters that may occur in the input; choose a padding value
compatible with that alphabet.
public static String protectReference(String value) {
if (value == null || value.isEmpty()) {
return value;
}
String alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return exate.processFPE(value, attributeCode, alphabet, "0");
}
processFPE is reversible only when reconstruction uses the same attribute,
alphabet, padding value, tenant, and relevant job context. Do not use it in a
Restrict job: the service rejects that job type.
getDeterministicIndex
Use getDeterministicIndex to make a stable selection from a fixed set without
storing a mapping. For example, select one of three approved replacements:
public static String selectReplacement(String value) {
String[] replacements = {"alpha", "bravo", "charlie"};
int index = exate.getDeterministicIndex(value, attributeCode, replacements.length);
return replacements[index];
}
The index is deterministic for the same value and context, but changes when
relevant context such as the snapshot date or tenant changes. Never pass 0
for maxVal—that causes division by zero.
Advanced function reuse and status
Most custom PETs should call process directly. If processing a collection of
values, retrieve processFn once and reuse the returned function:
scala.Function1<Object, Object> processor = exate.processFn(attributeCode, 0L, "");
String first = String.valueOf(processor.apply("first value"));
String second = String.valueOf(processor.apply("second value"));
For Reconstruct or Restrict jobs, processStatusFn can be used before an
operation to obtain its status:
scala.Function1<Object, Object> statusFn = exate.processStatusFn(attributeCode, 0L, "");
boolean allowed = (Boolean) statusFn.apply(value);
Do not call processStatusFn in Data masking or Pseudonymise jobs; it raises
an unsupported-job-type error. Status is not a substitute for calling
process, and successful status does not guarantee that every downstream
operation will succeed.
Logging and safe diagnostics
Use logger.trace, debug, info, warn, and error for Java custom PET
diagnostics. Log actions, attribute codes, result categories, and input
lengths—not the value itself. For example:
logger.info("CUSTOM_PET action=normalise attribute=" + attributeCode
+ " inputLength=" + (value == null ? 0 : value.length()));
Log visibility follows the deployment's LOG_LEVEL (error, warn, info,
debug, or trace). With IS_CONSOLE_LOGGING_ENABLED=true, eligible platform
log records are also output as JSON to the console.
Never log personal data, transformed values, tokens, credentials, keys, or the
complete jobConfig. If custom code fails, the platform logs the detailed
error and returns a generic internal-error response. Investigate with the
request trace ID and platform logs.
Test checklist
- Test null, empty, normal, Unicode, and maximum-length inputs.
- Test each job type in which the custom PET will run.
- For
processFPE, test a protect/reconstruct pair with identical FPE parameters. - For nested pseudonymisation, verify
postProcess()is called and that an authorised user can reconstruct the protected result. - Confirm that logs contain no personal or secret data.