Scala access modifiers and qualifiers in detail

Just like Java and other object-oriented programming languages, Scala has access modifiers to restrict access to members of classes, traits, objects and packages. Scala’s access modifiers are slightly different than Java’s; in this post I explain the difference. Besides modifiers, Scala also has qualifiers which allow more fine-grained access control. This is a feature that doesn’t exist in Java.

Access modifiers in Java and Scala

Java has four access levels:

Java access levels
Modifier Class Package Subclass World
public Y Y Y Y
protected Y Y Y N
no modifier Y Y N N
private Y N N N

Scala has three access levels, and they differ slightly from Java’s levels:

Scala access levels
Modifier Class Companion Subclass Package World
no modifier Y Y Y Y Y
protected Y Y Y N * N
private Y Y N N * N

*: Top-level protected and private members are accessible from inside the package.

Notable differences between Java and Scala are:

  • There is no public keyword in Scala. The default access level (when no modifier is specified) corresponds to Java’s public access level.
  • Unlike in Java, in Scala members that are protected are not accessible from other unrelated classes, traits or objects in the same package.
  • There is no direct equivalent to Java’s default access level in Scala.
  • In Scala, members that are private are accessible not only inside the class itself, but also in the class’ companion object (and vice versa: a private member in an object is accessible also in the object’s companion class).
  • You can make a top-level type private or protected in Scala (see below); this is not possible in Java.

In my opinion, Scala’s access levels make more sense than Java’s; the default access level in Java (package-wide access) is hardly ever used in practice, so it’s strange to have this as the default, and the fact that protected members are accessible to unrelated classes in the same package is most of the time not what you want. In Java, it’s not possible to make members accessible only to other members in the class itself and to subclasses, but not other classes in the same package.

Private top-level types

In Java, top-level classes and interfaces can only have one of two access levels: public or the default (package-scope) access level. You cannot make a top-level class or interface private or protected in Java.

In Java and also in Scala, when you make a nested class private, it means that the class is accessible only from the enclosing class. The meaning of private for a top-level class in Scala is just an extension of this idea, but now the enclosing scope is the package in which the class is defined instead of an enclosing class.

So, at first sight, it looks like a private top-level class in Scala is the same as a top-level class in Java with no access modifier; in both cases, the class is accessible only from the package that it is defined in. But this is not entirely correct. In Java, a class with no access modifier is not accessible in a nested package:

// Java example
package com.jesperdj.example;

// Top-level class with package-level accessibility
class Example {
    public void sayHello() {
        System.out.println("Hello World");
    }
}
// Nested package (under com.jesperdj.example)
package com.jesperdj.example.nested;

// ERROR: class Example is not accessible
import com.jesperdj.example.Example;

public class FromNestedPackage {
    public void method() {
        // ERROR: class Example is not accessible
        new Example().sayHello();
    }
}

In Scala, a private top-level class is also accessible from nested packages. This makes sense, since the enclosing scope of both the private class and the nested package is the same (it’s the package inside which both are defined), and private means that the class is accessible from anywhere within its enclosing scope.

// Scala example
package com.jesperdj.example

// Private top-level class
private class Example {
  def sayHello(): Unit = println("Hello World")
}
// Nested package (under com.jesperdj.example)
package com.jesperdj.example.nested

class FromNestedPackage {
  // OK: class Example is accessible because this is in the same enclosing scope
  def method(): Unit = new Example().sayHello()
}

This difference comes from the fact that the idea of nested packages do not really exist in Java, even though it seems that way. The Java compiler treats the packages com.jesperdj.example and com.jesperdj.example.nested as two unrelated packages. The fact that the second package looks like it is inside the first package doesn’t mean anything to the Java compiler.

The Scala compiler does have a notion of nested packages, which can also be seen by the fact that we didn’t have to import the class com.jesperdj.example.Example like we would have to do in Java.

Another difference is that Scala does not allow you to let a private class escape from its enclosing scope. For example, Scala doesn’t allow you to write a public method that returns a private class. This makes sense, since the method can be called from anywhere because it’s public, but its return type is not accessible from everywhere.

// Scala example
package com.jesperdj.example

class ExampleFactory {
  // Public method which returns an instance of a private class
  // ERROR: private class Example escapes its defining scope
  def createExample(): Example = new Example
}

The Java compiler doesn’t complain when you write a public method that returns a package-scope class. Instead of complaining about the method definition, it’s going to complain at the point where you try to use the package-scope class.

// Java example
package com.jesperdj.example;

public class ExampleFactory {
    // OK: Public method which returns an instance of a package-scope class
    public Example createExample() {
        return new Example();
    }
}
// Different package
package com.jesperdj.other;

// ERROR: class Example is not accessible
import com.jesperdj.example.Example;

public class SomewhereElse {
    public void method() {
        // ERROR: class Example is not accessible
        Example example = new ExampleFactory().createExample();
    }
}

What the Scala compiler does is better; it reports the error closer to the actual source of the error. Java will let you write a library with a method that returns an inaccessible type, and you wouldn’t discover the error until somebody would try to use your library.

Protected top-level types

Even though Scala allows it, there is not much benefit to make a top-level class protected instead of private – they effectively mean the same thing.

To understand this, think about nested classes. When you make a nested class protected instead of private, it means its accessible not only in its enclosing class, but also in subclasses of its enclosing class. For top-level classes, the enclosing scope is a package instead of a class. Since there is no inheritance between packages (a package cannot extend another package, like a class can extend a superclass), protected doesn’t mean something else than private from the viewpoint of packages.

Access qualifiers

Scala’s protected and private access modifiers have an interesting feature that give you more fine-grained control. You can add a qualifier after the keywords protected and private, between square brackets. The qualifier can be one of two things: the name of an enclosing scope (package, class, trait or object), or the keyword this. These two kinds of qualifiers have different meanings.

Qualified access to an enclosing scope

With a qualifier that refers to an enclosing scope, you can widen the accessibility of a private or protected member to that scope, so that it’s not only accessible in the immediate enclosing scope, but in a scope that is one or more levels higher up. For example:

package outside {
  package inside {
    object Messages {
      // Accessible up to package 'inside'
      private[inside] val Insiders = "Hello Friends"

      // Accessible up to package 'outside'
      private[outside] val Outsiders = "Hello People"
    }

    object InsideGreeter {
      def sayHello(): Unit =
        // Can access both messages
        println(Messages.Insiders + " and " + Messages.Outsiders)
    }
  }

  object OutsideGreeter {
    def sayHello(): Unit =
      // Can only access the 'Outsiders' message
      println(inside.Messages.Outsiders)
  }
}

Note that the name between the square brackets must be a simple name; it cannot be a fully-qualified name (with parts separated by dots).

Qualified access to ‘this’

Normally, in both Java and Scala, when a member of a class or trait is private or protected, this means that the access is restricted to the class or trait. Access is not restricted to individual instances; an instance of a class can access the same member in other instances of the same class.

In Scala it is possible to restrict access to the same instance only by qualifying the access modifier with [this], making the member object-private or object-protected. This means that an instance of a class or trait can only access the member in the current instance, and not in other instances. For example:

class Counter {
  // Normal private member variable
  private var total = 0

  // Object-private member variable
  private[this] var lastAdded = 0

  def add(n: Int): Unit = {
    total += n
    lastAdded = n
  }

  def copyFrom(other: Counter): Unit = {
    // OK, private member from other instance is accessible
    total = other.total

    // ERROR, object-private member from other instance is not accessible
    lastAdded = other.lastAdded
  }
}

Conclusion

The access modifiers in Scala are slightly different than in Java. In my opinion, they are better in Scala, for the following reasons:

  • Public is the default in Scala, which is more useful than Java’s default access level, which is rarely useful in practice in Java code.
  • protected has the same meaning in Scala as in C++; only accessible in the class itself and in subclasses. Fortunately Scala didn’t inherit Java’s weird idea, where protected members are also accessible in unrelated classes that happen to be in the same package.
  • The meaning of private top-level types in Scala makes sense.
  • When you accidentally expose a type that has restricted access from a less restricted method, Scala gives you an error at the declaration of the method (the actual source of the error) rather than at the point where you try to call the method.
  • With qualifiers, and because Scala understands nested packages, you have more fine-grained control over accessibility in Scala than in Java.
  • In Scala you can make members really really private by restricting access to only the current instance, which isn’t possible in Java.

In this post I’ve descibed Scala’s access control in detail. It’s possible to go even deeper, by looking at how the Scala compiler implements these features at the JVM-level. I suspect that the class file format that the JVM uses is really geared towards Java’s access control model, and I wonder how Scala manages to implements its own features, which are different and go beyond Java’s model, using what the class file format and the JVM offer. That will be a topic for another day.

You may also like...

6 Responses

  1. gertjanassies says:

    nice post, One accessibility trick most people don’t know about is the private keyword before the parameters on a case class: case class Foo private(bar:String), in combination with a apply method in the companion object, for instance Object Foo { def apply(bar:String) = new Foo(bar.toUppercase) }

  2. sindhu says:

    nice post can I get the syntax on how to write syntax of qualifier for giving access to other class of the same package not at the Package level acess

    mentioned below

    private[package name] val variable name = “#####”

    private[syntax for class/traits] val variable name = “#####”

    • Jesper de Jong says:

      Hi Sindhu, there is no way to give an arbitrary other class in the package access – it only works if the class that contains the private[…] member is a nested class, then you can give the enclosing class access to the member of the nested class.