Java 21 – Scoped Values

How do you pass a value from one part of your code to another.

Let’s say from one class to another.

By using method arguments.

Is there any other way that you can access a shared variable?

For example,

Let’s say you need to pass the user name who logged into your web app from one part of the app to another , let’s say from the controller layer to the database access layer.

Before allowing access to the database , you want to check if the user has enough priviliges or not.

One way is to pass the user name as a method argument all the way from the starting layer of your application to the database layer.

Every method in a particular workflow will have the username as an argument.

There is a better way to do this!

Every web request is handled by a single thread in a web application.

And you can create a variable particular to the scope of the thread, just like you create variables within the scope of a method or class!

Thread Variables allow you to do this.

Here is an example from Open JDK docs:

class Server {
    final static ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();  

    void serve(Request request, Response response) {
        var level     = (request.isAuthorized() ? ADMIN : GUEST);
        var principal = new Principal(level);
        PRINCIPAL.set(principal);                                         
        Application.handle(request, response);
    }
}

The above code refers to the server code.

YOU DON’T WRITE THIS CODE.

Ideally this piece of code will be written in a framework like Spring Boot.

But you can also do something similar in your code.

You see how for every request the web server serves , a thread local variable called PRINCIPAL is created.

This is a final static field.

This variable PRINCIPAL will be common across the thread.

You can access it anywhere in the application where the thread flows.

For example , if the request is directed to the database layer using method calls , you can access it there .

Here is a sample code again from Open JDK docs on how to do that:

class DBAccess {
    DBConnection open() {
        var principal = Server.PRINCIPAL.get();                           
      
           ....                                     
    }
}

This is the code that you write.

See how you access the value of the PRINCIPAL value through get method.

You are not passing the PRINCIPAL variable as a method argument.

It is set once by the framework and then you can access it anywhere in the code!

Instead of the framework setting the thread local variable as seen in the above example, you can also set your own thread local variable and access it anywhere.

There are few issues with ThreadLocal variables though:

  1. It is mutable

You can set a thread local variable and then update it later in any part of the code.

You do this using set() method just like you did while creation.

This can lead to confusion particularly because it is not easy to find out where the variable is updated again.

This can also lead to flaws in your code logic when you are expecting a particular value because you set it somewhere but later it got updated to some other value and so you get a different value.

2. Memory leaks:

Thread Local variables exist in memory as long as the thread lives.

You can manually destroy the variable but developers can often forget to do so.

This can lead to memory leaks.

3. Expensive inheritance

If you create a thread variable for a particular thread and then create a child thread for that thread, one more copy of the variable is created for the child thread.

If your application generates millions of threads (including parent and child), you will then have millions of copies !

Scoped Values:

To resolve the above issues , Java came up with Scoped Values.

  1. It is immutable

Scoped Values unlike thread variables are immutable.

Once created you cannot change its value.

2. It is destroyed automatically

When you create a scoped variable , you also define its scope.

And the variable gets destroyed once the scope is over.

3. Child threads share parent thread’s copy

Since scoped values are immutable , it makes no sense to create a new copy for the child threads.

So they share the copy of the parent thread.

This saves a lot of memory if you have millions of threads generated.

Now let’s see how to create a scoped value.

Let’s update the server code with scoped values instead of thread local variables.

class Server {
    final static ScopedValue<Principal> PRINCIPAL
        = ScopedValue.newInstance();                                        

    void serve(Request request, Response response) {
        var level     = (request.isAdmin() ? ADMIN : GUEST);
        var principal = new Principal(level);
        ScopedValue.where(PRINCIPAL, principal)                             
                   .run(() -> Application.handle(request, response));
    }
}

As you see Scoped Value is declared as a final static variable just like thread local variables.

But they are created using Factory method newInstance()

Then while creating the scoped value you call the where() method and then you set the scope by calling the subsequent code inside run() method.

It follows the below format:

ScopedValue.where().run()

The PRINCIPAL variable will now be available within the scope of the lambda function passed to run method.

Also it is available further down the method call.

In the above example Application.handle() method is passed as a lambda function.

Application.handle() method in turn can call another method where you can access the scoped value .

That method in turn can call another method where also the scoped value will be accessible.

But what if you want to return a value from the lambda function.

Then you can use call() method instead of run() method.

You can use the below format:

ScopedValue.where().call()

This will return a value .

class Server {
    final static ScopedValue<Principal> PRINCIPAL
        = ScopedValue.newInstance();                                        

    void serve(Request request, Response response) {
        var level     = (request.isAdmin() ? ADMIN : GUEST);
        var principal = new Principal(level);
      var returnValue =   ScopedValue.where(PRINCIPAL, principal)                             
                   .call(() -> Application.handle(request, response));
    }
}

Once set you can access the variable in your code like this:

class DBAccess {
    DBConnection open() {
        var principal = Server.PRINCIPAL.get();                             
     
       ...
    }
}

This is similar to thread local variable , you get the value of the variable by calling get() method.

That’s it!


Posted

in

, ,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading