<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Using Scala and Java UDFs in Unity Catalog in MVP Articles</title>
    <link>https://community.databricks.com/t5/mvp-articles/using-scala-and-java-udfs-in-unity-catalog/m-p/163450#M252</link>
    <description>&lt;DIV class=""&gt;&lt;SPAN&gt;A Complete Guide to Scala and Java UDFs in Unity&amp;nbsp;Catalog&lt;/SPAN&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_7-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29176i7837B53229308F37/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_7-1784517495938.png" alt="Dataninsight_7-1784517495938.png" /&gt;&lt;/span&gt;&lt;P class=""&gt;Imagine your company stores customer phone numbers from around the world. Some come in as (415) 555-0132, others as +44 20 7946 0958, others as 020-7946-0958. They all mean something valid, but no two look the same which makes it nearly impossible to deduplicate customers, send SMS reminders through a third-party provider, or join phone-based records across systems.&lt;/P&gt;&lt;P class=""&gt;The fix already exists: a well-tested Scala or Java library called libphonenumber (built by Google) knows how to parse almost any phone number format on Earth and convert it into one consistent standard, called &lt;STRONG&gt;E.164&lt;/STRONG&gt; (e.g. +14155550132). The problem isn't the logic, it's that this logic lives in a JVM library, and your analysts query everything in SQL.&lt;/P&gt;&lt;P class=""&gt;This guide walks through solving that problem end to end: writing the logic once, registering it in Databricks Unity Catalog, and making it callable by anyone on your team who knows SQL with no Scala knowledge required on their end.&lt;/P&gt;&lt;H3&gt;A few terms to&amp;nbsp;know:&lt;/H3&gt;&lt;P class=""&gt;&lt;BR /&gt;&lt;STRONG&gt;UDF&lt;/STRONG&gt; (User-Defined Function) A custom function you write yourself, because the built-in SUM, UPPER, ROUND functions don't do what you need.&lt;BR /&gt;&lt;STRONG&gt;Unity Catalog &lt;/STRONG&gt;Databricks' governance layer: it controls who can see and run what, across your whole organization.&lt;BR /&gt;&lt;STRONG&gt;JAR &lt;/STRONG&gt;A single packaged file containing compiled Java/Scala code (and, if it's a "JAR," all the external libraries it depends on too).&lt;BR /&gt;&lt;STRONG&gt;Volume&amp;nbsp;&lt;/STRONG&gt;A managed folder inside Unity Catalog where you can store files like JARs, so Databricks compute can access them.&lt;BR /&gt;&lt;STRONG&gt;Handler&amp;nbsp;&lt;/STRONG&gt;The exact method Databricks should call when your UDF runs you point at it by its full package path.&lt;/P&gt;&lt;P class=""&gt;If you’ve never touched Scala before, that’s fine you’ll copy the handler code as-is. The part you’ll actually interact with day to day is the SQL at the very end.&lt;/P&gt;&lt;H3&gt;Why this needs to be a governed UDF, not just a notebook&amp;nbsp;function&lt;/H3&gt;&lt;P class=""&gt;Before Unity Catalog UDFs existed, you had two bad options:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;Define the function in every notebook.&lt;/STRONG&gt; It disappears when the session ends. Nobody outside that notebook can use it. If the logic needs a fix, you’re hunting down every copy of it.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Rewrite it in Python.&lt;/STRONG&gt; Possible, but risky libphonenumber's Python port lags behind the Java version, and rewriting battle-tested logic introduces new bugs.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;Registering it in Unity Catalog instead means:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;You write it once.&lt;/STRONG&gt; Everyone, SQL users, notebook users, job authors calls the same function.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It’s governed.&lt;/STRONG&gt; You decide exactly who can run it, via standard permission grants.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It’s discoverable.&lt;/STRONG&gt; Anyone can find it in Catalog Explorer instead of asking around on Slack.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It works everywhere.&lt;/STRONG&gt; classic compute, serverless notebooks and jobs, and SQL warehouses.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;What you’ll need before&amp;nbsp;starting&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;A Databricks workspace with Unity Catalog enabled&lt;/LI&gt;&lt;LI&gt;USAGE and CREATE FUNCTION permission on the target schema, and USAGE on the catalog&lt;/LI&gt;&lt;LI&gt;A local machine with &lt;STRONG&gt;JDK 17&lt;/STRONG&gt; and &lt;STRONG&gt;sbt&lt;/STRONG&gt; installed (for building the JAR)&lt;/LI&gt;&lt;/UL&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_8-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29175i80C06AE963428909/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_8-1784517495938.png" alt="Dataninsight_8-1784517495938.png" /&gt;&lt;/span&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;Scala 2.13.16&lt;/STRONG&gt; (Scala 2.12 isn’t supported)&lt;/LI&gt;&lt;LI&gt;A Unity Catalog volume to store the JAR, and READ VOLUME permission on it (you'll create this in Step 3 if it doesn't exist)&lt;/LI&gt;&lt;/UL&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_9-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29177i3919643AA505A5CC/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_9-1784517495938.png" alt="Dataninsight_9-1784517495938.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;HR /&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;H3&gt;Step 1: Write the&amp;nbsp;function&lt;/H3&gt;&lt;P class=""&gt;Every Unity Catalog Scala UDF has one rule that trips people up the first time:&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;the handler must be a method on a Scala &lt;STRONG&gt;object&lt;/STRONG&gt;, not a class. Create a new file at src/main/scala/com/acme/udf/PhoneUDF.scala:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;package com.acme.udf&lt;BR /&gt;import com.google.i18n.phonenumbers.PhoneNumberUtil&lt;BR /&gt;object PhoneUDF {&lt;BR /&gt;  private val util = PhoneNumberUtil.getInstance()&lt;BR /&gt;  def toE164(raw: String, region: String): String = {&lt;BR /&gt;    if (raw == null) return null&lt;BR /&gt;    val parsed = util.parse(raw, region)&lt;BR /&gt;    util.format(parsed, PhoneNumberUtil.PhoneNumberFormat.E164)&lt;BR /&gt;  }&lt;BR /&gt;}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_10-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29179iCC19465DC693C0CA/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_10-1784517495938.png" alt="Dataninsight_10-1784517495938.png" /&gt;&lt;/span&gt;&lt;P class=""&gt;&lt;STRONG&gt;What this does, in plain terms:&lt;/STRONG&gt; toE164 takes a messy phone number plus the country it's from (so it knows how to interpret the local format), and hands back the clean, standardized version.&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;The if (raw == null) return null line matters, without it, a single blank phone number in your dataset could crash the whole query.&lt;/P&gt;&lt;H3&gt;Step 2: Tell your build tool about the dependency&lt;/H3&gt;&lt;P class=""&gt;The PhoneNumberUtil class comes from Google's libphonenumber library, not from Scala's standard library so your project needs to know to include it. Create or edit build.sbt in your project root:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;scalaVersion := "2.13.16"&lt;BR /&gt;ThisBuild / organization := "com.acme"&lt;BR /&gt;lazy val phoneUdf = (project in file("."))&lt;BR /&gt;  .settings(&lt;BR /&gt;    name := "phone-udf",&lt;BR /&gt;    libraryDependencies += "com.googlecode.libphonenumber" % "libphonenumber" % "8.13.35"&lt;BR /&gt;  )&lt;/SPAN&gt;&lt;/PRE&gt;&lt;H3&gt;Step 3: Enable the plugin that bundles everything together&lt;/H3&gt;&lt;P class=""&gt;Databricks needs a single, self-contained file: your code &lt;EM&gt;and&lt;/EM&gt;&amp;nbsp;&lt;BR /&gt;every library it depends on called a “JAR.”&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;Create project/assembly.sbt:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_11-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29178i67F64E7F45BFA3AF/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_11-1784517495938.png" alt="Dataninsight_11-1784517495938.png" /&gt;&lt;/span&gt;&lt;H3&gt;Step 4: Build the&amp;nbsp;JAR&lt;/H3&gt;&lt;P class=""&gt;From your project’s root directory, run:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;sbt clean assembly&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_12-1784517495939.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29180i9AD5DE1F9B3B7C1F/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_12-1784517495939.png" alt="Dataninsight_12-1784517495939.png" /&gt;&lt;/span&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;This produces a file like target/scala-2.13/phone-udf-assembly-0.1.0.jar. That single file now contains your handler code &lt;EM&gt;and&lt;/EM&gt; the libphonenumber library it needs nothing else to install later.&lt;/P&gt;&lt;H3&gt;Step 5: Upload the JAR to a Unity Catalog&amp;nbsp;volume&lt;/H3&gt;&lt;P class=""&gt;If you don’t already have a volume to store JARs, create one:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; VOLUME IF &lt;SPAN class=""&gt;NOT&lt;/SPAN&gt; &lt;SPAN class=""&gt;EXISTS&lt;/SPAN&gt; acme.telco.jars&lt;BR /&gt;COMMENT &lt;SPAN class=""&gt;'Storage for UDF JAR files'&lt;/SPAN&gt;;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Then upload the file itself through the UI:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;In your Databricks workspace, open &lt;STRONG&gt;Catalog Explorer&lt;/STRONG&gt;.&lt;/LI&gt;&lt;LI&gt;Navigate to the catalog and schema containing your volume (acme → telco → jars).&lt;/LI&gt;&lt;LI&gt;Click &lt;STRONG&gt;Upload to this volume&lt;/STRONG&gt; and select your JAR file.&lt;/LI&gt;&lt;LI&gt;Once uploaded, click the file name, then &lt;STRONG&gt;Copy path&lt;/STRONG&gt;.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;You’ll get a path that looks like: /Volumes/acme/telco/jars/phone-udf-assembly-0.1.0.jar&lt;/P&gt;&lt;P class=""&gt;Keep that path handy, you need it in the next step.&lt;/P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_13-1784517495939.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29181i73C39EACBD9BF16D/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_13-1784517495939.png" alt="Dataninsight_13-1784517495939.png" /&gt;&lt;/span&gt;&lt;H3&gt;Step 6: Register the function in Unity&amp;nbsp;Catalog&lt;/H3&gt;&lt;P class=""&gt;This is the step that turns your JAR into something callable from SQL:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; &lt;SPAN class=""&gt;OR&lt;/SPAN&gt; REPLACE &lt;SPAN class=""&gt;FUNCTION&lt;/SPAN&gt; acme.telco.to_e164(raw STRING, region STRING)&lt;BR /&gt;&lt;SPAN class=""&gt;RETURNS&lt;/SPAN&gt; STRING&lt;BR /&gt;&lt;SPAN class=""&gt;LANGUAGE&lt;/SPAN&gt; SCALA&lt;BR /&gt;ENVIRONMENT (&lt;BR /&gt;  java_dependencies &lt;SPAN class=""&gt;=&lt;/SPAN&gt; &lt;SPAN class=""&gt;'["/Volumes/acme/telco/jars/phone-udf-assembly-0.1.0.jar"]'&lt;/SPAN&gt;,&lt;BR /&gt;  environment_version &lt;SPAN class=""&gt;=&lt;/SPAN&gt; &lt;SPAN class=""&gt;'4'&lt;/SPAN&gt;&lt;BR /&gt;)&lt;BR /&gt;HANDLER &lt;SPAN class=""&gt;'com.acme.udf.PhoneUDF.toE164'&lt;/SPAN&gt;;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Note: If getting syntax error for now wait for some days.&lt;/P&gt;&lt;P class=""&gt;Breaking down what each part actually means:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;acme.telco.to_e164&lt;/STRONG&gt;&amp;nbsp;: the full name your function will have in SQL: catalog, schema, function name.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;RETURNS STRING&lt;/STRONG&gt;&amp;nbsp;: must match the handler's return type exactly.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;ENVIRONMENT&lt;/STRONG&gt;&amp;nbsp;: this is where you point at the JAR you just uploaded, and declare environment_version = '4', which tells Databricks to use Scala 2.13.16 and JDK 17 to run it (required for Scala/Java UDFs).&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;HANDLER&lt;/STRONG&gt;&amp;nbsp;: the exact object and method path: package.Object.method.&lt;/LI&gt;&lt;/UL&gt;&lt;P class=""&gt;You need USAGE + CREATE FUNCTION on the telco schema, and USAGE on the acme catalog, to run this successfully.&lt;/P&gt;&lt;H3&gt;Step 7: Decide who’s allowed to use&amp;nbsp;it&lt;/H3&gt;&lt;P class=""&gt;By default, only you (the creator) can run the new function. Grant access to whoever needs it:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;GRANT&lt;/SPAN&gt; &lt;SPAN class=""&gt;EXECUTE&lt;/SPAN&gt; &lt;SPAN class=""&gt;ON&lt;/SPAN&gt; &lt;SPAN class=""&gt;FUNCTION&lt;/SPAN&gt; acme.telco.to_e164 &lt;SPAN class=""&gt;TO&lt;/SPAN&gt; `data&lt;SPAN class=""&gt;-&lt;/SPAN&gt;analysts`;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;They’ll also need USAGE on the acme catalog and telco schema, without that, EXECUTE alone won't let them call it.&lt;/P&gt;&lt;H3&gt;Step 8: Actually use&amp;nbsp;it&lt;/H3&gt;&lt;P class=""&gt;This is the payoff, from here on, nobody needs to think about Scala, JARs, or volumes again. It’s just SQL:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;-- &lt;SPAN class=""&gt;Call&lt;/SPAN&gt; it directly&lt;BR /&gt;&lt;SPAN class=""&gt;SELECT&lt;/SPAN&gt; id, to_e164(phone, country_code) &lt;SPAN class=""&gt;AS&lt;/SPAN&gt; phone_e164&lt;BR /&gt;&lt;SPAN class=""&gt;FROM&lt;/SPAN&gt; acme.telco.customers;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;For a cleaner handoff to other teams, wrap it in a view so they never even see the raw function call:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; &lt;SPAN class=""&gt;OR&lt;/SPAN&gt; REPLACE &lt;SPAN class=""&gt;VIEW&lt;/SPAN&gt; acme.telco.customers_clean &lt;SPAN class=""&gt;AS&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN class=""&gt;SELECT&lt;/SPAN&gt; id, to_e164(phone, country_code) &lt;SPAN class=""&gt;AS&lt;/SPAN&gt; phone_e164&lt;BR /&gt;&lt;SPAN class=""&gt;FROM&lt;/SPAN&gt; acme.telco.customers;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Now anyone can simply query acme.telco.customers_clean and get clean, standardized phone numbers with the underlying logic fully governed and auditable.&lt;/P&gt;&lt;H3&gt;Step 9: Updating the logic&amp;nbsp;later&lt;/H3&gt;&lt;P class=""&gt;Business logic changes. When it does, you don’t touch the function definition you rebuild and replace:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;Update your Scala code.&lt;/LI&gt;&lt;LI&gt;Rebuild with a new version number: sbt clean assembly → phone-udf-assembly-0.2.0.jar.&lt;/LI&gt;&lt;LI&gt;Upload the new JAR to the same volume.&lt;/LI&gt;&lt;LI&gt;Re-run CREATE OR REPLACE FUNCTION, pointing java_dependencies at the new path.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;No cluster restart needed: the next call to the function picks up the new code automatically.&lt;/P&gt;&lt;H3&gt;Common issues to watch&amp;nbsp;for&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;“Function not found” after registering&lt;/STRONG&gt; double check you’re using the fully qualified name (catalog.schema.function), and that you have USAGE on both the catalog and schema.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Registration fails on classic compute&lt;/STRONG&gt; confirm you’re on Databricks Runtime 18.2 or above; earlier versions don’t support this feature.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;UDF returns NULL unexpectedly&lt;/STRONG&gt; if a parameter is a primitive type (like Int), a NULL input silently short-circuits to NULL output. Wrap it in Option[Int] if you need to handle nulls explicitly yourself.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;JAR built but “class not found” at call time&lt;/STRONG&gt; this usually means the JAR wasn’t built as a JAR (i.e., the assembly plugin wasn’t set up correctly), so the dependency isn’t actually bundled inside it.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;Why this is worth&amp;nbsp;doing&lt;/H3&gt;&lt;P class=""&gt;None of this is complicated once you’ve done it once: write the function, package it, upload it, register it, grant access, call it. What it makes you is bigger than the mechanics:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;The same phone-normalization logic runs everywhere no more three slightly-different Python reimplementations across three teams.&lt;/LI&gt;&lt;LI&gt;It’s auditable: one place to see who can run it, one JAR to version.&lt;/LI&gt;&lt;LI&gt;The people using it in SQL every day never need to learn Scala, understand JARs, or know a volume exists. They just call to_e164(...) like it was always part of SQL.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;Further reading&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/udf/scala-java-uc" target="_blank" rel="noopener"&gt;Scala and Java UDFs in Unity Catalog&lt;/A&gt;&lt;/LI&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/udf/scala" target="_blank" rel="noopener"&gt;Session-scoped Scala and Java UDFs&lt;/A&gt; for quick, one-off logic that doesn’t need to be shared&lt;/LI&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/data-governance/unity-catalog/manage-privileges/" target="_blank" rel="noopener"&gt;Manage privileges in Unity Catalog&lt;/A&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/DIV&gt;&lt;/DIV&gt;</description>
    <pubDate>Mon, 20 Jul 2026 03:24:01 GMT</pubDate>
    <dc:creator>Dataninsight</dc:creator>
    <dc:date>2026-07-20T03:24:01Z</dc:date>
    <item>
      <title>Using Scala and Java UDFs in Unity Catalog</title>
      <link>https://community.databricks.com/t5/mvp-articles/using-scala-and-java-udfs-in-unity-catalog/m-p/163450#M252</link>
      <description>&lt;DIV class=""&gt;&lt;SPAN&gt;A Complete Guide to Scala and Java UDFs in Unity&amp;nbsp;Catalog&lt;/SPAN&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_7-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29176i7837B53229308F37/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_7-1784517495938.png" alt="Dataninsight_7-1784517495938.png" /&gt;&lt;/span&gt;&lt;P class=""&gt;Imagine your company stores customer phone numbers from around the world. Some come in as (415) 555-0132, others as +44 20 7946 0958, others as 020-7946-0958. They all mean something valid, but no two look the same which makes it nearly impossible to deduplicate customers, send SMS reminders through a third-party provider, or join phone-based records across systems.&lt;/P&gt;&lt;P class=""&gt;The fix already exists: a well-tested Scala or Java library called libphonenumber (built by Google) knows how to parse almost any phone number format on Earth and convert it into one consistent standard, called &lt;STRONG&gt;E.164&lt;/STRONG&gt; (e.g. +14155550132). The problem isn't the logic, it's that this logic lives in a JVM library, and your analysts query everything in SQL.&lt;/P&gt;&lt;P class=""&gt;This guide walks through solving that problem end to end: writing the logic once, registering it in Databricks Unity Catalog, and making it callable by anyone on your team who knows SQL with no Scala knowledge required on their end.&lt;/P&gt;&lt;H3&gt;A few terms to&amp;nbsp;know:&lt;/H3&gt;&lt;P class=""&gt;&lt;BR /&gt;&lt;STRONG&gt;UDF&lt;/STRONG&gt; (User-Defined Function) A custom function you write yourself, because the built-in SUM, UPPER, ROUND functions don't do what you need.&lt;BR /&gt;&lt;STRONG&gt;Unity Catalog &lt;/STRONG&gt;Databricks' governance layer: it controls who can see and run what, across your whole organization.&lt;BR /&gt;&lt;STRONG&gt;JAR &lt;/STRONG&gt;A single packaged file containing compiled Java/Scala code (and, if it's a "JAR," all the external libraries it depends on too).&lt;BR /&gt;&lt;STRONG&gt;Volume&amp;nbsp;&lt;/STRONG&gt;A managed folder inside Unity Catalog where you can store files like JARs, so Databricks compute can access them.&lt;BR /&gt;&lt;STRONG&gt;Handler&amp;nbsp;&lt;/STRONG&gt;The exact method Databricks should call when your UDF runs you point at it by its full package path.&lt;/P&gt;&lt;P class=""&gt;If you’ve never touched Scala before, that’s fine you’ll copy the handler code as-is. The part you’ll actually interact with day to day is the SQL at the very end.&lt;/P&gt;&lt;H3&gt;Why this needs to be a governed UDF, not just a notebook&amp;nbsp;function&lt;/H3&gt;&lt;P class=""&gt;Before Unity Catalog UDFs existed, you had two bad options:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;Define the function in every notebook.&lt;/STRONG&gt; It disappears when the session ends. Nobody outside that notebook can use it. If the logic needs a fix, you’re hunting down every copy of it.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Rewrite it in Python.&lt;/STRONG&gt; Possible, but risky libphonenumber's Python port lags behind the Java version, and rewriting battle-tested logic introduces new bugs.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;Registering it in Unity Catalog instead means:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;You write it once.&lt;/STRONG&gt; Everyone, SQL users, notebook users, job authors calls the same function.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It’s governed.&lt;/STRONG&gt; You decide exactly who can run it, via standard permission grants.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It’s discoverable.&lt;/STRONG&gt; Anyone can find it in Catalog Explorer instead of asking around on Slack.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;It works everywhere.&lt;/STRONG&gt; classic compute, serverless notebooks and jobs, and SQL warehouses.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;What you’ll need before&amp;nbsp;starting&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;A Databricks workspace with Unity Catalog enabled&lt;/LI&gt;&lt;LI&gt;USAGE and CREATE FUNCTION permission on the target schema, and USAGE on the catalog&lt;/LI&gt;&lt;LI&gt;A local machine with &lt;STRONG&gt;JDK 17&lt;/STRONG&gt; and &lt;STRONG&gt;sbt&lt;/STRONG&gt; installed (for building the JAR)&lt;/LI&gt;&lt;/UL&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_8-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29175i80C06AE963428909/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_8-1784517495938.png" alt="Dataninsight_8-1784517495938.png" /&gt;&lt;/span&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;Scala 2.13.16&lt;/STRONG&gt; (Scala 2.12 isn’t supported)&lt;/LI&gt;&lt;LI&gt;A Unity Catalog volume to store the JAR, and READ VOLUME permission on it (you'll create this in Step 3 if it doesn't exist)&lt;/LI&gt;&lt;/UL&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_9-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29177i3919643AA505A5CC/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_9-1784517495938.png" alt="Dataninsight_9-1784517495938.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;HR /&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;H3&gt;Step 1: Write the&amp;nbsp;function&lt;/H3&gt;&lt;P class=""&gt;Every Unity Catalog Scala UDF has one rule that trips people up the first time:&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;the handler must be a method on a Scala &lt;STRONG&gt;object&lt;/STRONG&gt;, not a class. Create a new file at src/main/scala/com/acme/udf/PhoneUDF.scala:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;package com.acme.udf&lt;BR /&gt;import com.google.i18n.phonenumbers.PhoneNumberUtil&lt;BR /&gt;object PhoneUDF {&lt;BR /&gt;  private val util = PhoneNumberUtil.getInstance()&lt;BR /&gt;  def toE164(raw: String, region: String): String = {&lt;BR /&gt;    if (raw == null) return null&lt;BR /&gt;    val parsed = util.parse(raw, region)&lt;BR /&gt;    util.format(parsed, PhoneNumberUtil.PhoneNumberFormat.E164)&lt;BR /&gt;  }&lt;BR /&gt;}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_10-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29179iCC19465DC693C0CA/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_10-1784517495938.png" alt="Dataninsight_10-1784517495938.png" /&gt;&lt;/span&gt;&lt;P class=""&gt;&lt;STRONG&gt;What this does, in plain terms:&lt;/STRONG&gt; toE164 takes a messy phone number plus the country it's from (so it knows how to interpret the local format), and hands back the clean, standardized version.&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;The if (raw == null) return null line matters, without it, a single blank phone number in your dataset could crash the whole query.&lt;/P&gt;&lt;H3&gt;Step 2: Tell your build tool about the dependency&lt;/H3&gt;&lt;P class=""&gt;The PhoneNumberUtil class comes from Google's libphonenumber library, not from Scala's standard library so your project needs to know to include it. Create or edit build.sbt in your project root:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;scalaVersion := "2.13.16"&lt;BR /&gt;ThisBuild / organization := "com.acme"&lt;BR /&gt;lazy val phoneUdf = (project in file("."))&lt;BR /&gt;  .settings(&lt;BR /&gt;    name := "phone-udf",&lt;BR /&gt;    libraryDependencies += "com.googlecode.libphonenumber" % "libphonenumber" % "8.13.35"&lt;BR /&gt;  )&lt;/SPAN&gt;&lt;/PRE&gt;&lt;H3&gt;Step 3: Enable the plugin that bundles everything together&lt;/H3&gt;&lt;P class=""&gt;Databricks needs a single, self-contained file: your code &lt;EM&gt;and&lt;/EM&gt;&amp;nbsp;&lt;BR /&gt;every library it depends on called a “JAR.”&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;Create project/assembly.sbt:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;%scala&lt;BR /&gt;addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_11-1784517495938.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29178i67F64E7F45BFA3AF/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_11-1784517495938.png" alt="Dataninsight_11-1784517495938.png" /&gt;&lt;/span&gt;&lt;H3&gt;Step 4: Build the&amp;nbsp;JAR&lt;/H3&gt;&lt;P class=""&gt;From your project’s root directory, run:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;sbt clean assembly&lt;/SPAN&gt;&lt;/PRE&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_12-1784517495939.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29180i9AD5DE1F9B3B7C1F/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_12-1784517495939.png" alt="Dataninsight_12-1784517495939.png" /&gt;&lt;/span&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P class=""&gt;This produces a file like target/scala-2.13/phone-udf-assembly-0.1.0.jar. That single file now contains your handler code &lt;EM&gt;and&lt;/EM&gt; the libphonenumber library it needs nothing else to install later.&lt;/P&gt;&lt;H3&gt;Step 5: Upload the JAR to a Unity Catalog&amp;nbsp;volume&lt;/H3&gt;&lt;P class=""&gt;If you don’t already have a volume to store JARs, create one:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; VOLUME IF &lt;SPAN class=""&gt;NOT&lt;/SPAN&gt; &lt;SPAN class=""&gt;EXISTS&lt;/SPAN&gt; acme.telco.jars&lt;BR /&gt;COMMENT &lt;SPAN class=""&gt;'Storage for UDF JAR files'&lt;/SPAN&gt;;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Then upload the file itself through the UI:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;In your Databricks workspace, open &lt;STRONG&gt;Catalog Explorer&lt;/STRONG&gt;.&lt;/LI&gt;&lt;LI&gt;Navigate to the catalog and schema containing your volume (acme → telco → jars).&lt;/LI&gt;&lt;LI&gt;Click &lt;STRONG&gt;Upload to this volume&lt;/STRONG&gt; and select your JAR file.&lt;/LI&gt;&lt;LI&gt;Once uploaded, click the file name, then &lt;STRONG&gt;Copy path&lt;/STRONG&gt;.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;You’ll get a path that looks like: /Volumes/acme/telco/jars/phone-udf-assembly-0.1.0.jar&lt;/P&gt;&lt;P class=""&gt;Keep that path handy, you need it in the next step.&lt;/P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dataninsight_13-1784517495939.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29181i73C39EACBD9BF16D/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Dataninsight_13-1784517495939.png" alt="Dataninsight_13-1784517495939.png" /&gt;&lt;/span&gt;&lt;H3&gt;Step 6: Register the function in Unity&amp;nbsp;Catalog&lt;/H3&gt;&lt;P class=""&gt;This is the step that turns your JAR into something callable from SQL:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; &lt;SPAN class=""&gt;OR&lt;/SPAN&gt; REPLACE &lt;SPAN class=""&gt;FUNCTION&lt;/SPAN&gt; acme.telco.to_e164(raw STRING, region STRING)&lt;BR /&gt;&lt;SPAN class=""&gt;RETURNS&lt;/SPAN&gt; STRING&lt;BR /&gt;&lt;SPAN class=""&gt;LANGUAGE&lt;/SPAN&gt; SCALA&lt;BR /&gt;ENVIRONMENT (&lt;BR /&gt;  java_dependencies &lt;SPAN class=""&gt;=&lt;/SPAN&gt; &lt;SPAN class=""&gt;'["/Volumes/acme/telco/jars/phone-udf-assembly-0.1.0.jar"]'&lt;/SPAN&gt;,&lt;BR /&gt;  environment_version &lt;SPAN class=""&gt;=&lt;/SPAN&gt; &lt;SPAN class=""&gt;'4'&lt;/SPAN&gt;&lt;BR /&gt;)&lt;BR /&gt;HANDLER &lt;SPAN class=""&gt;'com.acme.udf.PhoneUDF.toE164'&lt;/SPAN&gt;;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Note: If getting syntax error for now wait for some days.&lt;/P&gt;&lt;P class=""&gt;Breaking down what each part actually means:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;acme.telco.to_e164&lt;/STRONG&gt;&amp;nbsp;: the full name your function will have in SQL: catalog, schema, function name.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;RETURNS STRING&lt;/STRONG&gt;&amp;nbsp;: must match the handler's return type exactly.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;ENVIRONMENT&lt;/STRONG&gt;&amp;nbsp;: this is where you point at the JAR you just uploaded, and declare environment_version = '4', which tells Databricks to use Scala 2.13.16 and JDK 17 to run it (required for Scala/Java UDFs).&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;HANDLER&lt;/STRONG&gt;&amp;nbsp;: the exact object and method path: package.Object.method.&lt;/LI&gt;&lt;/UL&gt;&lt;P class=""&gt;You need USAGE + CREATE FUNCTION on the telco schema, and USAGE on the acme catalog, to run this successfully.&lt;/P&gt;&lt;H3&gt;Step 7: Decide who’s allowed to use&amp;nbsp;it&lt;/H3&gt;&lt;P class=""&gt;By default, only you (the creator) can run the new function. Grant access to whoever needs it:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;GRANT&lt;/SPAN&gt; &lt;SPAN class=""&gt;EXECUTE&lt;/SPAN&gt; &lt;SPAN class=""&gt;ON&lt;/SPAN&gt; &lt;SPAN class=""&gt;FUNCTION&lt;/SPAN&gt; acme.telco.to_e164 &lt;SPAN class=""&gt;TO&lt;/SPAN&gt; `data&lt;SPAN class=""&gt;-&lt;/SPAN&gt;analysts`;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;They’ll also need USAGE on the acme catalog and telco schema, without that, EXECUTE alone won't let them call it.&lt;/P&gt;&lt;H3&gt;Step 8: Actually use&amp;nbsp;it&lt;/H3&gt;&lt;P class=""&gt;This is the payoff, from here on, nobody needs to think about Scala, JARs, or volumes again. It’s just SQL:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;-- &lt;SPAN class=""&gt;Call&lt;/SPAN&gt; it directly&lt;BR /&gt;&lt;SPAN class=""&gt;SELECT&lt;/SPAN&gt; id, to_e164(phone, country_code) &lt;SPAN class=""&gt;AS&lt;/SPAN&gt; phone_e164&lt;BR /&gt;&lt;SPAN class=""&gt;FROM&lt;/SPAN&gt; acme.telco.customers;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;For a cleaner handoff to other teams, wrap it in a view so they never even see the raw function call:&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;&lt;SPAN class=""&gt;CREATE&lt;/SPAN&gt; &lt;SPAN class=""&gt;OR&lt;/SPAN&gt; REPLACE &lt;SPAN class=""&gt;VIEW&lt;/SPAN&gt; acme.telco.customers_clean &lt;SPAN class=""&gt;AS&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN class=""&gt;SELECT&lt;/SPAN&gt; id, to_e164(phone, country_code) &lt;SPAN class=""&gt;AS&lt;/SPAN&gt; phone_e164&lt;BR /&gt;&lt;SPAN class=""&gt;FROM&lt;/SPAN&gt; acme.telco.customers;&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P class=""&gt;Now anyone can simply query acme.telco.customers_clean and get clean, standardized phone numbers with the underlying logic fully governed and auditable.&lt;/P&gt;&lt;H3&gt;Step 9: Updating the logic&amp;nbsp;later&lt;/H3&gt;&lt;P class=""&gt;Business logic changes. When it does, you don’t touch the function definition you rebuild and replace:&lt;/P&gt;&lt;OL class=""&gt;&lt;LI&gt;Update your Scala code.&lt;/LI&gt;&lt;LI&gt;Rebuild with a new version number: sbt clean assembly → phone-udf-assembly-0.2.0.jar.&lt;/LI&gt;&lt;LI&gt;Upload the new JAR to the same volume.&lt;/LI&gt;&lt;LI&gt;Re-run CREATE OR REPLACE FUNCTION, pointing java_dependencies at the new path.&lt;/LI&gt;&lt;/OL&gt;&lt;P class=""&gt;No cluster restart needed: the next call to the function picks up the new code automatically.&lt;/P&gt;&lt;H3&gt;Common issues to watch&amp;nbsp;for&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;STRONG&gt;“Function not found” after registering&lt;/STRONG&gt; double check you’re using the fully qualified name (catalog.schema.function), and that you have USAGE on both the catalog and schema.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Registration fails on classic compute&lt;/STRONG&gt; confirm you’re on Databricks Runtime 18.2 or above; earlier versions don’t support this feature.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;UDF returns NULL unexpectedly&lt;/STRONG&gt; if a parameter is a primitive type (like Int), a NULL input silently short-circuits to NULL output. Wrap it in Option[Int] if you need to handle nulls explicitly yourself.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;JAR built but “class not found” at call time&lt;/STRONG&gt; this usually means the JAR wasn’t built as a JAR (i.e., the assembly plugin wasn’t set up correctly), so the dependency isn’t actually bundled inside it.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;Why this is worth&amp;nbsp;doing&lt;/H3&gt;&lt;P class=""&gt;None of this is complicated once you’ve done it once: write the function, package it, upload it, register it, grant access, call it. What it makes you is bigger than the mechanics:&lt;/P&gt;&lt;UL class=""&gt;&lt;LI&gt;The same phone-normalization logic runs everywhere no more three slightly-different Python reimplementations across three teams.&lt;/LI&gt;&lt;LI&gt;It’s auditable: one place to see who can run it, one JAR to version.&lt;/LI&gt;&lt;LI&gt;The people using it in SQL every day never need to learn Scala, understand JARs, or know a volume exists. They just call to_e164(...) like it was always part of SQL.&lt;/LI&gt;&lt;/UL&gt;&lt;H3&gt;Further reading&lt;/H3&gt;&lt;UL class=""&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/udf/scala-java-uc" target="_blank" rel="noopener"&gt;Scala and Java UDFs in Unity Catalog&lt;/A&gt;&lt;/LI&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/udf/scala" target="_blank" rel="noopener"&gt;Session-scoped Scala and Java UDFs&lt;/A&gt; for quick, one-off logic that doesn’t need to be shared&lt;/LI&gt;&lt;LI&gt;&lt;A class="" href="https://docs.databricks.com/gcp/en/data-governance/unity-catalog/manage-privileges/" target="_blank" rel="noopener"&gt;Manage privileges in Unity Catalog&lt;/A&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/DIV&gt;&lt;/DIV&gt;</description>
      <pubDate>Mon, 20 Jul 2026 03:24:01 GMT</pubDate>
      <guid>https://community.databricks.com/t5/mvp-articles/using-scala-and-java-udfs-in-unity-catalog/m-p/163450#M252</guid>
      <dc:creator>Dataninsight</dc:creator>
      <dc:date>2026-07-20T03:24:01Z</dc:date>
    </item>
  </channel>
</rss>

