Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Scala

Scala EE

3.00/5 (3 votes)
28 Apr 2020CPOL8 min read 5.5K  
An overview of how well Scala integrates into modern Java EE environment
This post is a summary of my Scala EE development experience. It may be useful if you're going to do Scala smooth and nicely, like I did.

"Scala EE" or "Probably the fastest way to get hated by both functional programmers and enterprise devs"

Java is old. It's really, really old. In fact, most of today's students did not exist at the time it was released. Still, Java is the most popular enterprise language ever, and Java EE makes it even better providing a centralized way of creating all the environment your application is intended to be run in. But it's still old. Java works well for kinda old-minded people like me: I'm okay with declarations like the following:

Java
public class Task implements Callable<String>, Serializable {
    @Override
    public String call() {
        String result = doSomething();
        return result;
    }
} 

If you agree the snippet above is good, then you're probably, well, "old". We're not always old in a physical manner (well, not all of us, at least), but we recall those times we were sitting next to our company's review desk complaining about "those kids who broke it all again". We know verbosity saves the day and implicit mixins are the root of all evil, but everything is moving forward and our industry does as well. In fact, Java EE has figured it out a long time ago: CDI answers dependency injection and events handling, JAX-RS moves focus out of monstrous JSPs, async Servlet 3.0 knocks into JDBC drivers developers' doors and so on.

But can we go further? Could we just switch the paradigm and move ahead with asynchronous networking, immutable structures and all that stuff? Not exactly. Our industry has created literally tons of code and we cannot just drop it. Instead of drastic technological breakage, we could just adopt new ideas without breaking our existing workflows. That's what I have made while creating my home page, jtalk.me. I was wondering if it's possible to use Scala with my old-n-nice Java EE environment, and yes, it is. Below is a summary of my "Scala EE" development experience. This may be useful if you're going to do Scala smooth and nicely, like I did.

Let's Get to Work!

First of all, let's find out how exactly Scala is different from Java for the VM. Here's a simple Scala class we will use in our comparison. It contains everything one would normally use in Scala class: properties, custom constructors, weird methods signatures and so on:

Java
class TestClass @Inject() (private var arg: String, b: String) extends Runnable 
                                               with Callable[String] 
                                               with Serializable
                                               with StrictLogging {

	private val privateImmutableField = "pif"
	private var privateMutableField = "pmf"

	@Inject
	var publicMutableField = b
	val publicImmutableField = "puif"

	def this() = this("a", "b")

	override def run(): Unit = logger.debug("run()")
	override def call = {
		logger.debug("call()")
		publicImmutableField + privateMutableField
	}
}

object TestClass {
	private val PCONSTANT_VAL = "pcv"
	val CONSTANT_VAL = "pucv"
	def apply(arg: String, b: String) = new TestClass(arg, b)
}

Now, let's make a trick: CFR decompiler comes in handy here. This is what comes out of it (I have replaced logging code with SLF4J-based one as it was too verbose):

Java
@Slf4j
@ScalaSignature(bytes="...")
public class TestClass implements Runnable, Callable<string>, Serializable {

	private String arg;

	private final String publicImmutableField;
	private final String privateImmutableField;

	@Inject
	private String publicMutableField;
	private String privateMutableField;

	public static TestClass apply(String string, String string2) {
		return TestClass$.MODULE$.apply(string, string2);
	}

	public static String CONSTANT_VAL() {
		return TestClass$.MODULE$.CONSTANT_VAL();
	}

	private String arg() { return this.arg; }
	private void arg_$eq(String x$1) { this.arg = x$1; }

	private String privateImmutableField() { return this.privateImmutableField; }
	private String privateMutableField() { return this.privateMutableField; }

	private void privateMutableField_$eq(String x$1) { this.privateMutableField = x$1; }
	public String publicImmutableField() { return this.publicImmutableField; }

	public String publicMutableField() { return this.publicMutableField; }
	public void publicMutableField_$eq(String x$1) { this.publicMutableField = x$1; }
	
	@Override
	public void run() {
		log.debug("run()");
	}

	@Override
	public String call() {
		log.debug("call()");
		return this.publicImmutableField() + this.privateMutableField();
	}

	@Inject
	public TestClass(String arg, String b) {
		this.arg = arg;
		this.privateImmutableField = "pif";
		this.privateMutableField = "pmf";
		this.publicImmutableField = "puif";
		this.publicMutableField = b;
	}

	public TestClass() {
		this("a", "b");
	}
}

Interesting so far:

  • All the fields are private despite their original visibility. Non-important as you will merely need public fields in your managed beans.
  • All the public fields have public accessor methods. This is all right as direct fields access is not the way managed beans are intended to be used anyway.
  • Constructors from object declaration has been reflected in our class as static methods. This is actually a recent Scala feature for better Scala-Java interoperability, but in our particular case, it might be a problem. I'll explain it below.
  • You can put an annotation to the default constructor. Still, it looks weird. We can live with that, I assume.
  • Fields accessors are not JavaBeans-compatible. This is kinda fixable.
  • Fields are kept and their annotations are kept as well. This is great, CDI will be pleased a lot.

About object's methods propagation on class level.

CDI and EJB containers have complications with static methods. As those cannot be proxied and may be misleading, they are not expected to be supported by containers. So, we should really avoid using Scala objects in Java EE environment.

JavaBeans requires us to write properties with those get/set methods. Scala uses its own properties convention, but old-style properties may still be required, for example, in JPA entities. Scala has an easy and convenient way of creating such properties with @BeanProperty annotation:

Java
@BeanProperty
var id: String

This is literally equal to:

Java
private String id;

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

// All the Scala-style properties magic here 

Now, once we're all-packed and well-prepared, we can just go and write an enterprise application in Scala, right? Well, not really. There are several pitfalls we must take care of before.

JPA Metamodel Generation

I like this fresh-new Criteria API. It's really type-safe and verbose and I use it whenever I can. But what would this API be without a proper metamodel? We need to generate it, and then we need to use it.

You really can skip metamodel (and this whole paragraph) by just applying string names to Root's/Join's getters, but why would you choose to use Criteria API in the first place if you skip all the sweet parts?

The problem with metamodel generation is that it's built upon Java's compile-time annotations processing, which is not really supported by Scala very well. In fact, Scala supports them and scalac will process all the necessary stuff with hibernate-modelgen in project's dependencies, but Scala-to-Java cooperation works poorly there. Scala will not generate metamodel for Java entities used by Scala classes. So, you'll need to either keep all your entities in Java and compile Java first, or keep all your entities in Scala and compile your Java last. I have found no way to break this, unfortunately.

JSF-specific Support

JSF is a weird technology: while being kinda modern and handy, it has its own pitfalls kept from the very beginning. One of those pitfalls is a weird <ui:repeat> limitations: it ignores all the Iterable<> stuff only working with lists and arrays. So, Scala's Lists will not work in any sense. Weird and tricky:

Java
// Nota bene: this import is really required
import scala.collection.JavaConversions._

@Named
@ViewScoped
@ManagedBean
def ArticleBean {
    private var comments: List[Comment] = _
    def getComments() = new java.util.ArrayList(comments)
}

However, all the other JSF parts work well including action methods, view events and all that stuff. This is quite expected from those decompiled samples we've seen above.

CDI Events Support

Except for tricky declarations, everything works as expected:

Java
@ManagedBean
@ApplicationScoped
class EventProcessor {
    def onEvent(@Observes(during = AFTER_SUCCESS) e: MyEvent): Unit = {
        someAction()
    }
}

This "during" thing is exciting. If you've missed it, you should really try it. From now on, all the event processors without transactional lifecycle support are just garbage from my point of view.

Container-provided Boundaries

Scala is all about laziness. Lazy initializers, lazy streams, lazy arguments and all those functors you use so easily. But there's a catch. There's always a catch, isn't it? So, how will all that lazy staff work in EJB environment? Hint: not so well.

Consider inspecting the following code:

Java
@Stateless
@LocalBean
class ArticleService {

    @PersistenceContext
    private var em: EntityManager = _

    @TransactionAttribute(REQUIRES_NEW)
    def loadComments(articleIds: List[String]): Stream[Comment] = articleIds.toStream
        .map(em.find(classOf[Article], _))
        .filter(_ != null)
        .flatMap(_.getComments)
        .filter(_ != null)
}

Looks nice, isn't it? But what if I tell you about javax.transaction.TransactionRequiredException? Our EntityManager will really walk out of scope very kindly and all the construct will fail unexpectedly once this stream gets used. Scala is not designed for managed environments and requires extreme care as well as good container knowledge.

You can also lose something out of your secured method this way, but who cares about this method-level security anyway?

Stateful Beans Pitfall

Why do we need stateful session beans? For an extended persistence context, of course. And how do we declare such a context? With type field of our @PersistenceContext annotation, of course. And type is a keyword in Scala...

Well, I can imagine a human being who is interested in slow and non-recyclable POJO stateful bean really is, but why shall we talk about such an unusual demand in the first place?

However, there's always a workaround, isn't it? And here it comes:

Java
@Stateful
@LocalBean
class ArticleEditor {

    @PersistenceContext(`type` = EXTENDED, synchronization = UNSYNCHRONIZED)
    private var em: EntityManager = _

    @TransactionAttribute(MANDATORY)
    def save() = em.joinTransaction()
}

val is Not Final!

While our life was so bright and shiny, here comes the dark part: Scala has no public static final thing at all. All your attempts on creating this will, in fact, result in a static method's bytecode instead of a nice field. This could be all right most of the time, but there's a tiny part of Java where method presence will ruin everything. This part is called "annotation argument".

Wouldn't it be nice to write like:

Java
object Constants {
    val FIELD_SIZE = 100
}

@Entity
class SomeEntity {
    @BeanProperty
    @Column(length = Constants.FIELD_SIZE)
    private var field: String = _
}

Well, in fact, this wouldn't work. No chance, end of the road, drop off. Scala will generate public static int FIELD_SIZE() { return 100; } thing here and your annotation will yell at you demanding its argument to be accessible at compile time. Then what about that:

Java
public class Constants {
    private static final int FIELD_SIZE = 100;
}

This will not work either. The reason is that scalac will not parse your Java class declared as an import. Instead, it will just couple them in a regular way complaining to you about this field's non-constant nature anyway. That's why I kept all my model entities in Java nevertheless.

Bonus Chapter

Since you have read that far, here's a simple bonus. Scala has a strange feature: one can declare both field and constructor arguments in the same place like:

Java
class SomeClass(private var myField) {
    def doSomethingWithThisField() = ...
}

But how do I declare an annotation on either field or constructor argument? Looks like Scala designers have not thought of it in time, so we have a huge workaround here:

Java
import scala.annotation.meta.field
import scala.annotation.meta.param

class SomeClass(@(Observes @param)(during = IN_PROGRESS) @(Inject @field) var f: String) {
    def doSomethingWithThisField() = ...
}

Yes, I know observers are pretty useless being placed at constructor's arguments. I was just unable to find a handy annotation with arguments to reflect how weird this workaround is.

This will produce nice bytecode decompiling like:

Java
public class SomeClass {
    
    @Inject
    public String f;

    public SomeClass(@Observes(during = AFTER_SUCCESS) String f) {
        this.f = f;
    }

    public void doSomethingWithThisField() {
        ...
    }
}

Public fields in managed beans are bad. And single-letter variables are bad either in most cases. But our case matches the exception: this code does not fit into my blog's page otherwise.

As a conclusion: Scala is, well, usable in Java EE environment. If you're ready for some trade-offs, you can easily use it getting some nice features like extended collections transformers, nice functional programming features, type inference and so on. However, you should really understand not only what you are writing, but also how it would work in runtime. If you are looking for a whole new universe but still not ready for a huge step into, you may give this "Scala EE" a try.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)