5

Since Java 9, calling objects defined in java.sql from within Scala throw a java.lang.SecurityException when used directly from a scala script.

Java Version: 10.0.1
Scala Version: 2.12.4
sbt Version: 1.2.0

The below screenshot is an entire minimum working example, with console output for a working and non-working version. Specifically: copying the script code into a class, and running it from that class, resolves the issue. Is there a way to write a Scala script that directly uses objects from java.sql?

build.sbt

name := "mypackage"
version := "0.1"
scalaVersion := "2.12.4"

libraryDependencies += "org.postgresql" % "postgresql" % "42.2.4"

broken-script.scala

import java.sql.{Connection, DriverManager}
import java.util.Properties

object Main {

  private def url = "jdbc:postgresql://localhost:5432/postgres"

  val credentials: Properties ={
    val properties = new Properties()
    properties.setProperty("user", "integration_test")
    properties.setProperty("password", "integration-pass")

    properties
  }

  def connect(): Connection =
    DriverManager.getConnection(url, credentials)


  def run(): Unit = {
    connect()
  }

}

Main.run()

As shown in the console output below, broken-script.scala encounters a SecurityException.

brokenScript.scala Output

:load scripts/broken-script.scala
Loading scripts/broken-script.scala...
import java.sql.{Connection, DriverManager}
import java.util.Properties
defined object Main
java.lang.securityException: Prohibited package name: java.sql
  at java.base/java/lang.ClassLoader.preDefineClass(ClassLoader.java:891)
  at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java 1007)
  at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
  at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:545)
  at java.base/java.net.URLClassLoader.access$100(URLClassLoader.java:83)
  at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:453)
  at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:447)
  at java.base/java.security.AccessController.doPrivileged(Native Method)
  at java.base/net.URLClassLoader.findClass(URLClassLoader.java:446)
  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:566)
  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:553)
  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)
  at Main$.connect(scripts/broken-script.scala:26)
  at Main$.run(scripts/broken-script.scala:30)

src/main/scala/mypackage/Main.scala

package mypackage

import java.sql.{Connection, DriverManager}
import java.util.Properties

object Main {

  private def url = "jdbc:postgresql://localhost:5432/postgres"

  val credentials: Properties ={
    val properties = new Properties()
    properties.setProperty("user", "integration_test")
    properties.setProperty("password", "integration-pass")

    properties
  }

  def connect(): Connection =
    DriverManager.getConnection(url, credentials)


  def run(): Unit = {
    connect()
  }

}

working-script.scala

mypackage.Main.run()

println("Success")

An image of the entire project.

enter image description here

theStrawMan
  • 235
  • 2
  • 9
Will Beason
  • 3,417
  • 2
  • 28
  • 46

3 Answers3

3

As Rich pointed out in the comments, the problem is using Java 9 or later because they changed how classloaders work. The non-ideal fix is to downgrade to Java 8.

To ensure Java 8 is installed (Linux):

sudo update-alternatives --display java

To default to Java 8:

sudo update-alternatives --config java

After switching to Java 8, everything works fine. The solution is not ideal because it relies on using a version of Java that won't be supported for much longer.

Will Beason
  • 3,417
  • 2
  • 28
  • 46
  • 1
    Pleased you found something that works, even if it's non-ideal. I might've thought sbt would be able to handle this somehow, so have added sbt tag. – theStrawMan Aug 16 '18 at 23:05
  • 1
    It does sound like an `sbt` bug. Hopefully someone will post a proper fix here and win some magic internet points. – Rich Aug 17 '18 at 14:35
2

Edit: See later comment on class-loaders.


I haven't ever had to reference java.sql myself so this is not from personal experience, but it looks like the same problem as in this question: java.lang.SecurityException: error when executing outside Eclipse, where the problem occurs only outside the IDE. Doesn't look like you have your own class or package starting with 'java', so it's not that. What do your classpaths look like? It is a problem with the way the IDE builds with dependencies, so I think will require action within Intellij rather than changing the script - something like this: How to build JARs from Intellij properly.

theStrawMan
  • 235
  • 2
  • 9
  • I'm actually building the JARs with sbt. The Intellij is just for showing the structure. I got the same behavior in an otherwise clean directory. Though for the script, all of the jars should just be downloaded as dependencies, right? – Will Beason Aug 14 '18 at 16:30
  • Ok, are you using an old version of sbt? It looks like some [class-loader things](https://stackoverflow.com/questions/46494112/classloaders-hierarchy-in-java-9/46494286) changed when Java 9 was introduced. – theStrawMan Aug 14 '18 at 23:39
  • I'm using sbt 1.2.0, the latest stable version. – Will Beason Aug 15 '18 at 15:34
  • Ok, so not sbt. In response to your question "Though for the script, all of the jars should just be downloaded as dependencies, right?", possibly. That would be done like [this](https://stackoverflow.com/questions/24238060/how-to-run-jar-generated-by-package-possibly-with-other-jars-under-lib). I would hope you wouldn't need that for something in the JDK itself, but possibly that's the only approach. Unfortunately I'm not sure there. – theStrawMan Aug 16 '18 at 00:18
2

This situation with SecurityException looks very weird. I tried to reproduce your issue on my machine, but I didn't face SecurityException despite I followed the described steps.

I used the next code:

import java.sql.DriverManager
import java.util.Properties

object PostgresTest extends App {

  def connect() = {
    val properties = new Properties()
    properties.setProperty("user", "postgres")
    properties.setProperty("password", "admin")

    println("Attempt to establish connection ...")

    val someMetaData = DriverManager.getConnection("jdbc:postgresql://localhost:5432/postgres", properties)
      .getMetaData
      .getDefaultTransactionIsolation

    println(s"If you see this line, it works fine: $someMetaData")
  }
}

I tried the different approaches:

  • with sbt console: :load /home/username/space/test-project/src/main/scala/PostgresTest.scala and PostgresTest.connect() - no SecurityException

  • sbt console and :paste mode, it works fine

  • sbt runMain with code above + call of connect()method - works fine
  • via IntelliJ wizard for scripts and the main method - both of them work fine.

SBT - 1.1.1 and Scala - 2.12.4

I suggest that can be a problem on IntelliJ side. If any of the scenarios which I described above wouldn't work for you, please, notify me.

  • For Java 10: (1) :load fails, (2) :paste fails, (3) runMain fails. I'm not sure what (4) is. For Java 8, they all pass. – Will Beason Aug 16 '18 at 13:47