Redact a Column with a User-Defined Function
This example builds a scalar Flink user-defined function (UDF), mask, that
keeps the last few characters of a string and replaces the rest with asterisks.
You upload it as an artifact, register it in an environment catalog, and call it
in a query to redact an email column. For the reference material behind each
step, see User-Defined Functions in Confluent Manager for Apache Flink.
Prerequisites
Complete the examples setup first. It provides the
test environment, the pool compute pool, artifact management, and the JDK
and Maven you use to build the function JAR.
Build the function JAR
The function extends ScalarFunction and needs only flink-table-common,
which is already on the CMF and Flink classpath, so declare that dependency as
provided. Compile for Java 11 to match the Flink cluster runtime.
MaskFunction.java:
package io.confluent.examples.flink.udf;
import org.apache.flink.table.functions.ScalarFunction;
public class MaskFunction extends ScalarFunction {
public String eval(String input, Integer visible) {
if (input == null) {
return null;
}
int keep = (visible == null || visible < 0) ? 0 : Math.min(visible, input.length());
int hide = input.length() - keep;
return "*".repeat(hide) + input.substring(hide);
}
}
Package it with this pom.xml. Because flink-table-common is provided,
the JAR carries only your class and needs no shading:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.confluent.examples.flink</groupId>
<artifactId>udfs</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<flink.version>1.19.1</flink.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-common</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>udfs</finalName>
</build>
</project>
Build it:
mvn package
The build writes target/udfs.jar.
Upload the JAR as an artifact
A UDF JAR is referenced by a cmf:// URI, so it does not need the connector or
format labels. Put the metadata in artifact.json:
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "Artifact",
"metadata": { "name": "udfs.jar" },
"spec": {}
}
Upload it:
curl -X POST http://cmf:8080/cmf/api/v1/environments/test/artifacts \
-F 'artifact=@artifact.json;type=application/json' \
-F 'file=@target/udfs.jar'
The response has status.version of 1.
Register the function
Register mask in the environment catalog with CREATE FUNCTION, pointing
USING JAR at the artifact. This is metadata only and completes immediately.
confluent flink statement create udf-create-mask \
--url http://cmf:8080 \
--environment test \
--compute-pool pool \
--catalog _env_test \
--database default \
--sql "CREATE FUNCTION mask AS 'io.confluent.examples.flink.udf.MaskFunction' USING JAR 'cmf://test/udfs.jar';"
The statement reaches phase COMPLETED with sqlKind of CREATE_FUNCTION.
Call the function
Call mask like any built-in function. The unqualified name resolves only
when _env_test and default are the current catalog and database, so start
the SQL shell with the same --catalog and --database flags:
confluent flink shell \
--url http://cmf:8080 \
--environment test \
--compute-pool pool \
--catalog _env_test \
--database default
This query redacts customer emails from the examples.marketplace.customers
datagen table, keeping the last four characters:
SELECT customer_id, email, mask(email, 4) AS masked_email FROM examples.marketplace.customers;
The statement runs on the compute pool, and the shell shows results such as:
customer_id email masked_email
3133 eufemia.jenkins@yahoo.com *********************.com
3130 anabel.sipes@gmail.com ******************.com
3006 billy.tillman@gmail.com *******************.com
Clean up
The shell prints the statement name when it submits the query. For example,
cli-2026-10-19-120056-c98ff916. Stop the query by deleting that statement, and
drop the function if you no longer need it:
confluent flink statement delete <statement-name> \
--url http://cmf:8080 --environment test
confluent flink statement create udf-drop-mask \
--url http://cmf:8080 --environment test --compute-pool pool \
--catalog _env_test --database default \
--sql "DROP FUNCTION IF EXISTS mask;"