Flink CDC UDFs
Learn how to create, register, and use Java user-defined functions (UDFs) in Flink CDC jobs. When built-in functions don't meet your needs, custom UDFs provide flexible ways to extend data processing capabilities.
User-defined functions (UDFs)
When built-in Flink CDC functions don't cover your data transformation logic—such as encrypting or decrypting fields, parsing custom data formats, or calling a third-party library—write a Java UDF and call it just like any built-in function.
The overall workflow has three steps:
-
Define — Write a Java class that implements the UDF interface.
-
Register — Declare the UDF in the
pipelinesection of your Flink CDC job configuration. -
Use — Call the UDF in
projectionandfilterexpressions inside atransformblock.
Define a Java UDF
To write a UDF in Java, first create a Maven project and add the following base dependency to your pom.xml:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cdc-common</artifactId>
<version>${Apache Flink CDC version}</version>
<scope>provided</scope>
</dependency>
Select the Apache Flink CDC version that matches your Ververica Runtime (VVR) engine version:
| VVR engine version | Apache Flink CDC version |
|---|---|
| 11.7 and later | 3.6.0 |
| 11.3 to 11.6 | 3.5.0 |
| 11.0 to 11.2 | 3.4.0 |
| 8.0.11 | 3.3.0 |
| 8.0.10 and earlier | 3.2.1 |
A Java class must satisfy the following requirements to work as a Flink CDC UDF:
-
Implements the
org.apache.flink.cdc.common.udf.UserDefinedFunctioninterface -
Has a public, parameterless constructor
-
Has at least one public method named
eval
Optionally, override the following methods for more control:
-
getReturnType— Explicitly specify the return type whenevalhas an ambiguous return type. -
openandclose— Add initialization and cleanup logic for lifecycle management.
The following example defines a UDF that increments an integer by 1:
public class AddOneFunctionClass implements UserDefinedFunction {
public Object eval(Integer num) {
return num + 1;
}
@Override
public DataType getReturnType() {
// The return type of eval is ambiguous (Object).
// Use getReturnType to declare the exact CDC type.
return DataTypes.INT();
}
@Override
public void open() throws Exception {
// Initialize resources here, for example, open a database connection.
}
@Override
public void close() throws Exception {
// Release resources here, for example, close a database connection.
}
}
Type mappings
The following table maps CDC column types to the corresponding Java types used in eval method parameters, return values, and getReturnType:
| CDC column type | Java class |
|---|---|
| BOOLEAN | java.lang.Boolean |
| TINYINT | java.lang.Byte |
| SMALLINT | java.lang.Short |
| INTEGER | java.lang.Integer |
| BIGINT | java.lang.Long |
| FLOAT | java.lang.Float |
| DOUBLE | java.lang.Double |
| DECIMAL | java.math.BigDecimal |
| DATE | java.time.LocalDate |
| TIME | java.time.LocalTime |
| TIMESTAMP | java.time.LocalDateTime |
| TIMESTAMP_TZ | java.time.ZonedDateTime |
| TIMESTAMP_LTZ | java.time.Instant |
| CHAR / VARCHAR / STRING | java.lang.String |
| BINARY / VARBINARY / BYTES | byte[] |
| ARRAY | java.util.List (element types map to the List generic parameter) |
| MAP | java.util.Map (key and value types map to the Map generic parameters) |
| ROW | java.util.List |
| VARIANT | org.apache.flink.cdc.common.types.variant.Variant |
The VARIANT class path differs from the Variant class path in Flink SQL.
Register a Java UDF
Add a user-defined-function entry to the pipeline section of your Flink CDC job configuration:
pipeline:
user-defined-function:
- name: inc
classpath: org.apache.flink.cdc.udf.examples.java.AddOneFunctionClass
- name: format
classpath: org.apache.flink.cdc.udf.examples.java.FormatFunctionClass
On the Configurations tab of your job draft, upload the JAR file for the specified classpath as an additional dependency in the More Configurations section. The name field sets the alias used in expressions—it doesn't need to match the class name.
Python UDF (public preview)
You can write inline UDFs in Python directly in Flink CDC YAML ingestion jobs, without creating a Maven project or compiling additional JAR packages.
To define UDFs with Python, you must upgrade to Ververica Runtime (VVR) 11.9 (preview editions included) or later.
This is an experimental feature.
Syntax rules
A Python UDF must meet the following requirements:
-
A top-level function named
evalmust be defined inpython-code. -
The
evalfunction must declare a return type annotation. -
The number of parameters of the
evalfunction must match the number of arguments passed when the UDF is called. -
Parameter type annotations are not required, but keeping them is recommended for code readability.
-
Python UDFs currently support only scalar functions. Each invocation returns a single value.
-
SQL
NULLvalues in the input data are converted to PythonNone. -
A Python function can return
None. In that case, the output is SQLNULL.
For example, the following Python UDF trims whitespace from the input string and converts it to lowercase:
def eval(value: str) -> str:
return value.strip().lower()
You can define helper functions, import modules, or declare constants in python-code, but the eval function that performs the data transformation must be at the top level of the code. For example:
import re
EMAIL_PATTERN = re.compile(r"\s+")
def normalize(value):
return EMAIL_PATTERN.sub("", value).lower()
def eval(value: str) -> str:
if value is None: return None
return normalize(value)
You can import built-in Python packages at the top level of the code block. For the complete list, see Pre-installed packages.
Type mappings
Flink CDC determines the output column type of a UDF based on the return type annotation of the eval function. Currently, only the following parameter types and return value types are supported:
|
Python return type annotation |
CDC column type |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Register a Python UDF
To register a Python UDF, configure the python-code parameter under pipeline.user-defined-function of a Flink CDC ingestion job. The YAML | multi-line string syntax lets you write indented Python code blocks naturally:
pipeline:
user-defined-function:
- name: normalize_email
python-code: |
def eval(value: str) -> str:
if value is None:
return None
return value.strip().lower()
- name: double_age
python-code: |
def eval(value: int) -> int:
if value is None:
return None
return value * 2
Here, name is the function name used in transform expressions and does not need to match the Python function name. The function that Flink CDC calls in the inline code is always eval.
Null and exception handling of Python UDFs
NULL values in an ingestion pipeline correspond to Python None. If a UDF is called with a NULL argument, the Python eval function receives None. If the Python eval function returns None, NULL is used as the UDF return value.
If the Python eval function throws a runtime exception, the transform operator triggers a failover. If your UDF may fail, catch exceptions with Python try/except statements to keep the job stable.
Python UDF parameters
Python UDFs support the following parameters.
|
Parameter |
Description |
Required |
Data type |
Default value |
Remarks |
|
|
The name of the UDF registered in the transform module. |
Yes |
STRING |
None |
UDF names must be unique within a job. |
|
|
The inline source code of the Python UDF. |
Yes |
STRING |
None |
It must contain a top-level function named |
|
|
Additional dependencies used by the Python UDF. |
No |
LIST<STRING> |
None |
If a Python UDF requires additional dependencies, package them into a .zip file and upload the file as an additional dependency. Note
Additional dependency files are placed in the For example, if the additional dependency file is |
Use a UDF
After registering a UDF, you can call it directly in the transform block, just like a built-in function:
transform:
- source-table: db\.*
projection: "*, inc(inc(inc(id))) as inc_id, format(id, 'id -> %d') as formatted_id"
filter: inc(id) < 100
Compatibility with Flink SQL UDFs
Flink SQL UDFs that extend ScalarFunction can be registered and called directly as Flink CDC UDFs. The following limitations apply:
-
Parameterization: Parameterized
ScalarFunctionclasses are not supported. -
Type annotations: Flink-style
TypeInformationannotations are ignored. -
Lifecycle hooks:
openandcloselifecycle hooks are not called.