7

I am trying to send a message using akka remote actors, where the case class is a subclass of a superclass taking argument in its constructor.

Here is a minimum example to reproduce the problem:

package com.tuvistavie.testremote

import akka.actor.{ Actor, ActorSystem, Props, ActorLogging }
import com.typesafe.config.ConfigFactory

abstract class Foo(val a: Int)
case class MessageFoo(override val a: Int) extends Foo(a)

object Sender {
  def main(args: Array[String]) {
    val system = ActorSystem("Sender", ConfigFactory.load.getConfig("sender"))
    val actor = system.actorFor("akka://Receiver@127.0.0.1:2552/user/receiver")
    actor ! MessageFoo(1)
  }
}

object Receiver {
  class ReceiverActor extends Actor with ActorLogging {
    def receive = {
      case m: MessageFoo => log.debug(m.toString)
    }
  }

  def main(args: Array[String]) {
    val system = ActorSystem("Receiver", ConfigFactory.load.getConfig("receiver"))
    val actor = system.actorOf(Props[ReceiverActor], "receiver")
  }
}

When running this code, I get the following error:

[ERROR] [06/26/2013 02:53:16.132] [Receiver-9] 
[NettyRemoteTransport(akka://Receiver@127.0.0.1:2552)] 
RemoteServerError@akka://Receiver@127.0.0.1:2552] Error[java.io.InvalidClassException: com.tuvistavie.testremote.MessageFoo; no valid constructor]

I think it is because the message cannot be deserialized (using akka.serialization.JavaSerializer), because of the parents' constructor. If it were only one or two messages I know I could write my own serializer, but I have plenty of case classes like this in my application.

Would there be any easy way to pass this kind of object using remote actors?

Tobias Heinicke
  • 1,778
  • 1
  • 11
  • 17
Daniel Perez
  • 6,335
  • 4
  • 24
  • 28

2 Answers2

10
class A(a: Int)
case class C() extends A(1)

Like cmbaxter's answer points out, this pattern, where the superclass of the case class does not have a no-arg constructor, leads to InvalidClassException on deserialization. Per cmbaxter's answer, avoiding this pattern is one solution.

But what's wrong in this pattern? The reason is documented in the API docs for Serializable:

To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

So the problem is that class A does not have a no-arg constructor, plus it is not Serializable. So a simple solution is to make it Serializable!

class A(a: Int) extends Serializable
case class C() extends A(1)
Daniel Darabos
  • 26,991
  • 10
  • 102
  • 114
  • works like a charm, quick win to just add an `extends` / `with` as apposed to restructuring to traits. – Brett Mar 02 '15 at 10:34
9

Things will work if you restructure like so:

trait Foo{
  val a:Int
}
case class MessageFoo(a:Int) extends Foo

I generally try and stay away from class inheritance with case classes. If I need to be able to refer to a set of case classes as an abstract type, I use traits instead.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • 1
    Can you please explain what is going on here? Neither the question's `MessageFoo` nor yours has a no-arg constructor. Why does one work with Java serialization when the other does not? – Daniel Darabos Jul 18 '14 at 10:16
  • @DanielDarabos, scala case classes are serializable. If you look at the generated java code for a case class you will see that it meets the contract for being serializable. – cmbaxter Jul 18 '14 at 10:37
  • But the case class in the question could not be deserialized, right? – Daniel Darabos Jul 18 '14 at 10:48
  • @DanielDarabos, I believe that is because he extended an abstract class with a constructor that had a single arg. I think this broke the ability to serialize that case class hence my suggestion to avoid using that structure and try with a trait instead. – cmbaxter Jul 18 '14 at 10:52
  • 1
    Thanks! Your suggestion works. It looks like the rule is that the superclass constructor of the case class must be no-arg. I just find this a puzzling limitation. But I'm not brave enough to look at the generated Java code :). – Daniel Darabos Jul 18 '14 at 10:57
  • 1
    Looks like it's actually not a Scala problem! I was just ignorant of Java serialization. I've added an answer with an alternative solution. It may be useful when your approach cannot be applied. – Daniel Darabos Jan 28 '15 at 10:55
  • I think this is the cleaner than other solution. +1 – Daniele Tentoni Jan 16 '21 at 17:12