Scala/Play, Part 9: Scala Traits for Java Developers - Contracts and Shared Behavior
I started noticing traits and implicits in Scala code at work before we reached them in this course. Traits turned out to be a reassuring place to start: as a Java/Spring developer, I already understood contracts and implementations. The new keyword became much less mysterious once I connected it to a small repository.
This Scala 2.13.18 lesson defines a policy lookup contract, implements it with a List, then gives the trait a working method. The policies are fictional. There is no database or framework wiring in this example.

A trait declares what a repository can do
trait PolicyRepository {
def find(number: String): Option[PolicySnapshot]
}
There is no equals sign or body after find. It is an abstract method: a concrete implementing class must supply the lookup. The contract accepts a String and promises an Option[PolicySnapshot], so absence is explicit.
PolicySnapshot is the case class from our earlier lessons, with number, basePremium and claimFreeYears fields. The trait does not choose how those snapshots are stored.
The Java comparison is straightforward:
public interface PolicyRepository {
Optional<PolicySnapshot> find(String number);
}
This is a useful starting comparison, not a claim that Scala traits and Java interfaces have identical capabilities in every situation.
The implementation uses a method we already know
class InMemoryPolicyRepository(policies: List[PolicySnapshot])
extends PolicyRepository {
override def find(number: String): Option[PolicySnapshot] =
policies.find(policy => policy.number == number)
}
Scala uses extends here where Java would use implements. The constructor receives our policies list. Without val or var, this parameter does not expose a public policies property.
The method body is List.find from Part 6: return the first matching snapshot as Some, or None when no match exists. We have introduced a new boundary around familiar behavior.
I wrote override to make the relationship explicit. In Scala 2 it is optional when implementing an abstract method. When replacing an inherited concrete method, it is required.
The declared type is the contract
val policies = List(
PolicySnapshot("POL-001", 600, 3),
PolicySnapshot("POL-002", 600, 2)
)
val repository: PolicyRepository =
new InMemoryPolicyRepository(policies)
repository.find("POL-001")
// Some(PolicySnapshot(POL-001,600,3))
repository.find("POL-999")
// None
The variable has declared type PolicyRepository. The actual object is an InMemoryPolicyRepository. Calling find through the trait executes the implementation on that object. This is the polymorphism I already know from Java.
That lets the calling code ask for the contract:
def describe(
number: String,
repository: PolicyRepository
): String =
repository.find(number)
.map(policy => "Found policy " + policy.number)
.getOrElse("Policy not found")
The caller turns an optional policy into display text. The repository performs the lookup. We pass the dependency explicitly as an ordinary argument; nothing is discovered or injected automatically.

A trait can also provide the method body
Now I add an existence check directly to the trait:
trait PolicyRepository {
def find(number: String): Option[PolicySnapshot]
def exists(number: String): Boolean =
find(number).isDefined
}
find is still abstract. exists is concrete because it has a body. It calls find and checks whether its result is defined: Some gives true, None gives false.
InMemoryPolicyRepository stays unchanged. It inherits exists, so both calls now work:
repository.exists("POL-001") // true
repository.exists("POL-999") // false
The point that matters is where the inner call goes. The inherited exists body executes on the repository object. Its find call therefore reaches InMemoryPolicyRepository.find. The trait can define shared behavior using a method whose implementation it leaves to a class.

The Java default-method connection
public interface PolicyRepository {
Optional<PolicySnapshot> find(String number);
default boolean exists(String number) {
return find(number).isPresent();
}
}
For this example, a Java default interface method is the closest comparison. Scala needs no default keyword: the method body makes exists concrete. A class can inherit it or replace it with an override.
A practical detail follows from our implementation: exists performs the find lookup each time. It is not a cached flag. A future storage implementation could provide a specialized exists method, but a future asynchronous repository would also require reconsidering this synchronous contract. Merely keeping the name would not make the APIs interchangeable.
Run the repository example
TraitLesson.scala at this lesson's commit exercises found and missing policies, plus an empty repository. From the lab root:
sbt "runMain learning.PremiumLesson"
Through the trait: Found policy POL-001
Missing number: Policy not found
Empty repository: Policy not found
Known policy exists: true
Missing policy exists: false
Policy in empty repository exists: false
The run passed our assertions and the earlier lesson checks. I now have a concrete distinction between an abstract declaration, an implementation, and inherited behavior. Next comes multiple parameter lists, followed by implicit parameters using this same repository contract.
Reference: Scala 2 Book: traits with abstract and concrete methods. Our course continues toward Scala 2.13.18, Play 3 and Apache Pekko.