@@ -68,6 +68,30 @@ def get_or_else_none(scala_option):
6868 return scala_option .get ()
6969
7070
71+ # Cache per JVM instance so version detection only happens once per session.
72+ _jvm_converters_cache : dict = {}
73+
74+
75+ def _get_converters (jvm ):
76+ """
77+ Return (style, converters) for the running Scala version.
78+ style='jdk' → scala.jdk.javaapi.CollectionConverters (Scala 2.13, Spark 4+)
79+ style='legacy' → scala.collection.JavaConverters (Scala 2.12, Spark 3.x)
80+ """
81+ key = id (jvm )
82+ if key not in _jvm_converters_cache :
83+ try :
84+ converters = jvm .scala .jdk .javaapi .CollectionConverters
85+ # On Scala 2.12, the path resolves to a JavaPackage placeholder (no class
86+ # exists), so attribute access succeeds but any method call raises TypeError.
87+ # Probe with an actual call to confirm the class is genuinely usable.
88+ converters .asScala (jvm .java .util .ArrayList ())
89+ _jvm_converters_cache [key ] = ("jdk" , converters )
90+ except Exception :
91+ _jvm_converters_cache [key ] = ("legacy" , jvm .scala .collection .JavaConverters )
92+ return _jvm_converters_cache [key ]
93+
94+
7195def to_scala_seq (jvm , iterable ):
7296 """
7397 Helper method to take an iterable and turn it into a Scala sequence
@@ -77,7 +101,23 @@ def to_scala_seq(jvm, iterable):
77101 Returns:
78102 Scala sequence
79103 """
80- return jvm .scala .collection .JavaConversions .iterableAsScalaIterable (iterable ).toSeq ()
104+ style , converters = _get_converters (jvm )
105+ if style == "jdk" :
106+ return converters .asScala (iterable ).toSeq ()
107+ return converters .iterableAsScalaIterableConverter (iterable ).asScala ().toSeq ()
108+
109+
110+ def empty_scala_seq (jvm ):
111+ """
112+ Returns an empty Scala immutable List (Nil), usable as Seq[_].
113+ Converts an empty ArrayList via .asScala().toList() to produce an immutable.List
114+ rather than a Stream, which is required for Py4J constructor/method lookup to
115+ succeed across both Scala 2.12 (Spark 3.x) and Scala 2.13 (Spark 4+).
116+ """
117+ style , converters = _get_converters (jvm )
118+ if style == "jdk" :
119+ return converters .asScala (jvm .java .util .ArrayList ()).toList ()
120+ return converters .iterableAsScalaIterableConverter (jvm .java .util .ArrayList ()).asScala ().toList ()
81121
82122
83123def to_scala_map (spark_session , d ):
@@ -89,15 +129,29 @@ def to_scala_map(spark_session, d):
89129 Returns:
90130 Scala map
91131 """
92- return spark_session ._jvm .PythonUtils .toScalaMap (d )
132+ jvm = spark_session ._jvm
133+ try :
134+ # PythonUtils.toScalaMap is a PySpark internal that may be removed in future versions.
135+ return jvm .PythonUtils .toScalaMap (d )
136+ except Exception :
137+ style , converters = _get_converters (jvm )
138+ if style == "jdk" :
139+ return converters .asScala (d ).toMap ()
140+ return converters .mapAsScalaMapConverter (d ).asScala ().toMap ()
93141
94142
95143def scala_map_to_dict (jvm , scala_map ):
96- return dict (jvm .scala .collection .JavaConversions .mapAsJavaMap (scala_map ))
144+ style , converters = _get_converters (jvm )
145+ if style == "jdk" :
146+ return dict (converters .asJava (scala_map ))
147+ return dict (converters .mapAsJavaMapConverter (scala_map ).asJava ())
97148
98149
99150def scala_map_to_java_map (jvm , scala_map ):
100- return jvm .scala .collection .JavaConversions .mapAsJavaMap (scala_map )
151+ style , converters = _get_converters (jvm )
152+ if style == "jdk" :
153+ return converters .asJava (scala_map )
154+ return converters .mapAsJavaMapConverter (scala_map ).asJava ()
101155
102156
103157def java_list_to_python_list (java_list : str , datatype ):
0 commit comments