This page is meant as a template for writing a KIP. To create a KIP choose Tools->Copy on this page and modify with your content and replace the heading with the next KIP number and a description of your issue. Replace anything in italics with your own description.


Current state: Under Discussion

Discussion thread: here



In Scala its considered best practice that when you use case class's you create them as final rather than non final (if possible). There are many reasons behind this

  • case class's are are representation of ADT's which is a functional structure designed to only hold data. case class's do not typically define any behavior, they may define methods but this is typically only calculations based on existing fields inside a case class
    • If you want classic style OOP where you want inheritance then you use a standard class instead
  • case class's already define a hashcode/equals/unapply who's definition becomes misleading if its overridden in a subclass. Critically code that uses a defined case class relies on the fact that the hashcode/equals/unapply definition for that case class is not arbitrarily override by a user
  • Using final guarantees optimal performance for the case class
    • It is true that the JDK classes are "effectively" final if no one subclasses the class during runtime however using final guarantees this performance contract.

See for a more in detail explanation of what can go wrong if you don't mark case class as final

Public Interfaces

  • There is a source breaking change for Kafka's Scala core if the user happened to subclass a case class. There are no binary incompatible changes

Proposed Changes

Here are the following changes

  • Any public top level case class is made final
  • For case classes that are defined within a class
    • If the case class is private then nothing is done
    • If the case class is public but should have been private (i.e. no public methods/fields reference that case class) then it is made private. If said case class was also defined within another class it is moved to top level
    • If the case class is public then it is made top level and made final
  • Style guide updated to mention that case class should always be defined as final

Compatibility, Deprecation, and Migration Plan

  • The only impact for users is if they would have made a subclass of a specific case class within Kafka core and used it within Kafka code.
    • As mentioned before, if a user actually did this this then they were likely already doing something wrong especially if they override equals/hashcode
    • There is one valid case of subclassing which could be adding additional methods to an already existing case class (without any other behavioral changes). In this case a user can simply use implicit final class (aka monkey patching/extension methods) to just add extra methods to an already existing class.
      • This technique is well documented and supported within Scala
      • You cannot override any existing methods (i.e. hashcode/equals/unapply) using this technique, you can only add methods. This makes it much safer
  • Internally in Kafka no real changes were needed apart from removing a subclassing of a case class in a single test (this was only done to adjust the pretty print of some case class if the test failed so its not critical)
  • Since 3.0.0 is a major release, doing this within 3.0.0 is ideal

Rejected Alternatives

None that I am aware of

  • No labels