All Products
Search
Document Center

Realtime Compute for Apache Flink:Python user-defined scalar functions (UDSFs)

Last Updated:Aug 13, 2026

A user-defined scalar function (UDSF) maps zero, one, or more scalar values to a single scalar value. Each input row produces exactly one output value.

This topic describes how to create, register, and use a Python UDSF in Realtime Compute for Apache Flink.

Limits

The following constraints apply when you develop Python user-defined functions (UDFs) in Realtime Compute for Apache Flink:

Constraint Requirement
Apache Flink version 1.12 and later
Python version Pre-installed on every workspace. VVR earlier than 8.0.11: Python 3.7.9. VVR 8.0.11 and later: Python 3.9.21.
JDK version JDK 8 and JDK 11. Third-party JAR packages must be compatible with JDK 8 or JDK 11.
Scala version Open-source Scala 2.11 only. Third-party JAR packages must be compatible with Scala 2.11.
Inline functions Supported only in VVR 11.9-preview1 and later.
Important

After upgrading to VVR 8.0.11 or later, test, deploy, and run your existing PyFlink drafts again to confirm compatibility.

Development methods

You can develop a Python UDSF in either of the following ways:

  • Package your Python code, upload the package, and register it as a function on the platform.

  • Declare the code logic as an inline function in a SQL statement.

If the logic of your UDSF is simple, consider developing it as an inline function.

Create a UDSF

The following steps use Windows as the example environment. Flink provides a sample repository that includes implementations for UDSFs, user-defined aggregate functions (UDAFs), and user-defined table-valued functions (UDTFs).
  1. Download and decompress python_demo-master to your local machine.

    This is a third-party GitHub repository. Access may be slow or intermittent.
  2. In PyCharm, choose File > Open and open the decompressed python_demo-master directory.

  3. Open udfs.py in the \python_demo-master\udx path and define your UDSF.

    from pyflink.table import DataTypes
    from pyflink.table.udf import udf
    
    @udf(result_type=DataTypes.STRING())
    def sub_string(s: str, begin: int, end: int):
        return s[begin:end]

    The sub_string example extracts characters from position begin to position end in the input string.

  4. From the \python_demo-master directory, run the following command to package the udx directory:

    zip -r python_demo.zip udx

    When python_demo.zip appears in \python_demo-master\, the package is ready.

Register a UDSF

After creating the package, register the UDSF in the Realtime Compute for Apache Flink console. For registration steps, see Manage user-defined functions (UDFs).

Use a UDSF

After registering the UDSF, use it in a Flink SQL job.

  1. Create a draft using Flink SQL. For details, see Job development overview. The following example calls ASI_UDSF (the registered name of your UDSF) to extract characters from positions 2 to 4 of the a field in the source table:

    CREATE TEMPORARY TABLE ASI_UDSF_Source (
      a VARCHAR,
      b INT,
      c INT
    ) WITH (
      'connector' = 'datagen'
    );
    
    CREATE TEMPORARY TABLE ASI_UDSF_Sink (
      a VARCHAR
    ) WITH (
      'connector' = 'blackhole'
    );
    
    INSERT INTO ASI_UDSF_Sink
    SELECT ASI_UDSF(a, 2, 4)
    FROM ASI_UDSF_Source;
  2. In the left-side navigation pane of the development console, choose O&M > Deployments. Find the deployment, then click Start in the Actions column. After the deployment starts, characters at positions 2–4 of the a field in ASI_UDSF_Source are written to ASI_UDSF_Sink.

Python inline scalar functions

An inline function embeds its implementation directly in a CREATE FUNCTION statement, so the function is defined and registered within the same SQL statement. The following example defines an inline function that masks email addresses. Declare the complete code logic between the $$ delimiters.

CREATE TEMPORARY FUNCTION mask_email(email STRING)
RETURNS STRING AS $$
if email is None:
    return None

name, separator, domain = email.partition("@")
if not separator:
    return email

return name[:1] + "***@" + domain
$$ LANGUAGE PYTHON;
Note
  1. Python is sensitive to indentation. Start the first-level code of the function body at column 0, and maintain correct relative indentation within code blocks.

  2. Inline functions do not support vectorized execution and cannot be declared as non-deterministic functions.

  3. Only TEMPORARY functions are supported. Function definitions cannot be persisted to the platform yet.

Asynchronous user-defined functions

For UDFs that perform I/O-intensive operations such as external database access or HTTP requests, use asynchronous user-defined functions. A single asynchronous UDF can handle multiple I/O requests concurrently, distributing wait time across requests and improving job throughput.

Limits

  • Supported only on VVR 11.7 and later. VVR PyFlink 11.7 or later is required. For details, see ververica-flink.

    pip3 install "ververica-flink>=11.7"

  • Only asynchronous user-defined scalar functions (UDSFs) are supported.

  • Only Python process mode is supported, that is, python.execution-mode=process.

  • Pandas asynchronous user-defined functions are not yet supported.

  • Inline functions are not yet supported.

Usage

An asynchronous user-defined function can be implemented as a Python async function or as a subclass of the asynchronous function class. Sample code is shown below.

import asyncio

from pyflink.table import DataTypes
from pyflink.table.udf import AsyncScalarFunction, udf


# Method 1: Use a Python async function
@udf(result_type=DataTypes.STRING())
async def async_api_call(product_id: str) -> str:
    await asyncio.sleep(0.05)
    return f"product_{product_id}"


# Method 2: Subclass the asynchronous function class
class AsyncUserLookup(AsyncScalarFunction):
    def open(self, function_context):
        self.cache = {}

    async def eval(self, user_id: str) -> str:
        if user_id in self.cache:
            return self.cache[user_id]

        await asyncio.sleep(0.05)
        result = f"user_{user_id}"
        self.cache[user_id] = result
        return result

    def close(self):
        self.cache.clear()


async_user_lookup = udf(
    AsyncUserLookup(),
    input_types=[DataTypes.STRING()],
    result_type=DataTypes.STRING()
)

Asynchronous user-defined functions are registered and used the same way as synchronous functions.

Configuration parameters

The following parameters control the runtime behavior of asynchronous user-defined functions.

Parameter

Default value

Description

table.exec.async-scalar.max-concurrent-operations

10

The maximum number of concurrent asynchronous calls per operator instance. Default value: 10.

table.exec.async-scalar.timeout

3 min

The timeout for a single asynchronous call.

table.exec.async-scalar.retry-strategy

FIXED_DELAY

The retry strategy after an asynchronous call fails. Supported values:

  • FIXED_DELAY: Retry after a fixed wait time.

  • NO_RETRY: Do not retry.

table.exec.async-scalar.retry-delay

100 ms

The wait time for fixed-delay retries.

Note

Effective only when table.exec.async-scalar.retry-strategy is set to FIXED_DELAY.

table.exec.async-scalar.max-attempts

3

The maximum number of attempts before an asynchronous call is considered failed.

Note

Effective only when table.exec.async-scalar.retry-strategy is set to FIXED_DELAY.