Scala/Play, Part 11: Scala 2 Implicit Classes - Where Did That Method Come From?
I had just written "POL-001".lookup, and Scala found a policy. Coming from Java, my reaction was fairly loud. A string appeared to have gained a business method, and the repository argument had disappeared from the call.
The explanation clicked when I expanded the expression into ordinary code: construct a wrapper, then call its method with a repository. This part of my Scala 2 learning lab follows that expansion first, before using the shortcuts.
Part 10 covered implicit parameters. Here I add implicit classes and connect the two. The examples use Scala 2.13.18 and fictional insurance data; Play and Apache Pekko remain later course topics.

Start with a wrapper I already understand
Suppose I want a policy number to produce a display label. An ordinary class can hold the string and provide that behavior:
class PolicyNumberOps(private val number: String) {
def asPolicyLabel: String = "Policy " + number
}
val policyNumber: String = "POL-001"
val wrapper = new PolicyNumberOps(policyNumber)
val label = wrapper.asPolicyLabel
private val number stores the constructor argument for use inside the wrapper. def declares the method; its final expression supplies the result. The method has no parameter list, so I access it without parentheses. Ops is just a conventional name for operations.
The values are concrete: the original string is "POL-001", the wrapper holds that string, and the method returns "Policy POL-001". The analogous Java call would be new PolicyNumberOps(policyNumber).asPolicyLabel(), given a Java wrapper with that method.
Let Scala supply the wrapping step
Now I place the class inside an object and add implicit:
object PolicyNumberSyntax {
implicit class PolicyNumberOps(private val number: String) {
def asPolicyLabel: String = "Policy " + number
}
}
PolicyNumberSyntax groups the operations. Scala 2 requires implicit classes to be nested inside an object, class, or trait. The implicit class declaration also generates an implicit conversion that constructs the wrapper from its string argument. The official Scala 2 documentation describes this mechanism and its restrictions.
At the call site, I opt in:
import PolicyNumberSyntax._
val policyNumber: String = "POL-001"
val label = policyNumber.asPolicyLabel
The underscore is Scala 2's wildcard import: it brings the object's members into scope, including the generated conversion. String has no asPolicyLabel member. With this conversion available, the compiler can adapt the receiver to PolicyNumberOps and call the wrapper's method.
The explicit equivalent is still available:
new PolicyNumberSyntax.PolicyNumberOps(policyNumber)
.asPolicyLabel
The method belongs to the wrapper. Java's String class has not changed, and policyNumber still has type String. The import changes which conversion the compiler can find in this scope; it does not globally modify every string.

A method on the wrapper can need a dependency
Formatting a label only needs the number. Finding a policy also needs a repository. Our existing contract already exposes:
def find(number: String): Option[PolicySnapshot]
I could add an ordinary method to the wrapper:
def lookup(repository: PolicyRepository): Option[PolicySnapshot] =
repository.find(number)
The constructor supplied number; this method call supplies repository. The body uses both. Applying Part 10's syntax only changes the method's parameter list:
def lookup(implicit repository: PolicyRepository): Option[PolicySnapshot] =
repository.find(number)
This method sits inside PolicyNumberOps, alongside asPolicyLabel. Its implicit parameter allows Scala to supply a suitable repository when I omit the argument. I can still pass it explicitly. The Scala documentation on implicit parameters describes that argument search.
Remove one explicit step at a time
Here is the repository used in the runnable example:
implicit val localStore: PolicyRepository =
new InMemoryPolicyRepository(
List(PolicySnapshot("POL-001", 600, 3))
)
With PolicyNumberSyntax._ imported, these three calls produce the same result:
// Explicit wrapper and explicit repository
new PolicyNumberSyntax.PolicyNumberOps(policyNumber).lookup(localStore)
// Scala supplies the wrapper; I supply the repository
policyNumber.lookup(localStore)
// Scala supplies the wrapper and the repository argument
policyNumber.lookup
There are two separate conveniences. The imported implicit class makes lookup available on this receiver. The implicit value supplies an argument to that method. In this example the wrapper stores the number, while each lookup call receives a repository.

My Spring comparison stops at ordinary dependency passing. The compiler selects the argument at the call site; there is no Spring container involved in this lab. At runtime, the selected repository object's find implementation performs the lookup.
The explicit form keeps control visible
Even with localStore available implicitly, I can choose an empty repository for one call:
val emptyStore: PolicyRepository =
new InMemoryPolicyRepository(List.empty[PolicySnapshot])
policyNumber.lookup(emptyStore) // None
This is useful when reading unfamiliar code: expand the receiver adaptation, then expose the method argument. A missing policy produces None after a successful call. A missing implicit dependency is a compilation problem, as explored in Part 10.
I would use syntax like this selectively. A short expression saves repetition, but readers still need to discover the operations and dependencies behind it. Giving syntax a clearly named object and keeping explicit calls available makes that investigation easier.
Run the evidence
The complete lesson at commit 7b43025 contains the wrapper and assertions. From the lab root:
sbt "runMain learning.PremiumLesson"
The run passed on Scala 2.13.18 with sbt 1.12.15. All three lookup forms returned Some(PolicySnapshot(POL-001,600,3)); an unknown number and the explicitly chosen empty repository returned None. The earlier lesson assertions passed too.
I still enjoy how surprising "POL-001".lookup looks from Java. Now I can expand it into code I recognize, and that makes the surprise useful.