Session
By default, Micronaut is a stateless HTTP server, however depending on your application requirements you may need the notion of HTTP sessions.
Micronaut provides this module inspired by Spring Session that enables this which currently has two implementations:
In-Memory sessions - which you should combine with a sticky session proxy if you plan to run multiple instances.
Redis sessions - In this case Redis stores sessions, and non-blocking I/O is used to read/write sessions to Redis.
The core of Micronaut Session implementation is io.micronaut.session.http.HttpSessionFilter.
The following configuration options are available for the SessionFilter Filter:
To enable support for in-memory sessions you just need the micronaut-session dependency:
implementation ("io.micronaut.session:micronaut-session" )<dependency >
<groupId >io.micronaut.session</groupId >
<artifactId >micronaut-session</artifactId >
</dependency >
To quickly get up and running with Redis sessions you must also have the redis-lettuce dependency in your build:
build.gradle
compile "io.micronaut-session:micronaut-session"
compile "io.micronaut.redis:micronaut-redis-lettuce"
And enable Redis sessions via configuration in the application configuration file:
Enabling Redis Sessions
redis.uri =redis://localhost:6379
micronaut.session.http.redis.enabled =trueredis :
uri : redis://localhost:6379
micronaut :
session :
http :
redis :
enabled : true [redis ]
uri = "redis://localhost:6379"
[micronaut .session .http .redis ]
enabled = true redis {
uri = 'redis://localhost:6379'
}
micronaut {
session {
http {
redis {
enabled = true
}
}
}
} {
redis = {
uri = "redis://localhost:6379"
}
micronaut = {
session = {
http = {
redis = {
enabled = true
}
}
}
}
} {
"redis" : {
"uri" : "redis://localhost:6379"
},
"micronaut" : {
"session" : {
"http" : {
"redis" : {
"enabled" : true
}
}
}
}
}
2.4 Configuring Session Resolution
By default, sessions are resolved using an HttpSessionFilter that looks for session identifiers via either an HTTP header (using the Authorization-Info or X-Auth-Token headers) or via a Cookie named SESSION.
You can disable either header resolution or cookie resolution via configuration in the application configuration file:
Disabling Cookie Resolution
micronaut.session.http.cookie =false
micronaut.session.http.header =truemicronaut :
session :
http :
cookie : false
header : true [micronaut .session .http ]
cookie = false
header = true micronaut {
session {
http {
cookie = false
header = true
}
}
} {
micronaut = {
session = {
http = {
cookie = false
header = true
}
}
}
} {
"micronaut" : {
"session" : {
"http" : {
"cookie" : false ,
"header" : true
}
}
}
}
The above configuration enables header resolution, but disables cookie resolution. You can also configure header and cookie names.
2.5 Working with Sessions
A Session can be retrieved with a parameter of type Session in a controller method. For example consider the following controller:
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.session.Session;
import io.micronaut.session.annotation.SessionValue;
import io.micronaut.core.annotation.Nullable;
@Controller ("/shopping" )
public class ShoppingController {
private static final String ATTR_CART = "cart" ; //
@Post ("/cart/{name}" )
Cart addItem (Session session , String name ) { //
Cart cart = session.get (ATTR_CART, Cart.class).orElseGet (() -> { //
Cart newCart = new Cart ();
session.put (ATTR_CART, newCart); //
return newCart;
});
cart.getItems ().add (name);
return cart;
}
} import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
@Controller ("/shopping" )
class ShoppingController {
companion object {
private const val ATTR_CART = "cart" //
}
@Post ("/cart/{name}" )
internal fun addItem (session: Session , name: String ): Cart { //
require (name.isNotBlank ()) { "Name cannot be blank" }
val cart = session.get (ATTR_CART, Cart::class .java).orElseGet { //
val newCart = Cart ()
session.put (ATTR_CART, newCart) //
newCart
}
cart.items.add (name)
return cart
}
} import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
import jakarta.annotation.Nullable
@Controller ("/shopping" )
class ShoppingController {
private static final String ATTR_CART = "cart" //
@Post ("/cart/{name}" )
Cart addItem (Session session , String name ) { //
Cart cart = session. get(ATTR_CART , Cart ). orElseGet({ -> //
Cart newCart = new Cart ()
session. put(ATTR_CART , newCart) //
newCart
})
cart. items << name
cart
}
}
Note that because the Session is declared as a required parameter, to execute the controller action a Session will be created and saved to the SessionStore .
If you don’t want to create unnecessary sessions, declare the Session as @Nullable in which case a session will not be created and saved unnecessarily. For example:
Using @Nullable with Sessions
@Post ("/cart/clear" )
void clearCart (@Nullable Session session) {
if (session != null ) {
session.remove (ATTR_CART);
}
} @Post ("/cart/clear" )
internal fun clearCart (session: Session ?) {
session?.remove (ATTR_CART)
} @Post ("/cart/clear" )
void clearCart (@Nullable Session session ) {
session?. remove(ATTR_CART )
}
The above method only injects a new Session if one already exists.
If the client is a web browser, sessions should work if cookies are enabled. However, for programmatic HTTP clients you need to propagate the session ID between HTTP calls.
For example, when invoking the viewCart method of the StoreController in the previous example, the HTTP client receives by default a AUTHORIZATION_INFO header. The following example demonstrates this:
Retrieving the AUTHORIZATION_INFO header
when : "The shopping cart is retrieved"
HttpResponse<Cart> response = client. exchange(HttpRequest. GET ('/shopping/cart' ), Cart ) //
.blockFirst()
Cart cart = response . body()
then : "The shopping cart is present as well as a session id header"
response. header(HttpHeaders. AUTHORIZATION_INFO ) != null //
cart != null
cart. items. isEmpty()
You can then pass this AUTHORIZATION_INFO in subsequent requests to reuse the existing Session :
Sending the AUTHORIZATION_INFO header
String sessionId = response . header( HttpHeaders. AUTHORIZATION_INFO ) //
response = client. exchange(HttpRequest. POST ('/shopping/cart/Apple' , "" )
.header(HttpHeaders. AUTHORIZATION_INFO , sessionId), Cart ) //
.blockFirst()
cart = response. body()
Rather than explicitly injecting the Session into a controller method, you can instead use @SessionValue . For example:
Using @SessionValue
@Get ("/cart" )
@SessionValue (ATTR_CART) //
Cart viewCart (@SessionValue @Nullable Cart cart) { //
if (cart == null ) {
cart = new Cart ();
}
return cart;
} @Get ("/cart" )
@SessionValue (ATTR_CART) //
internal fun viewCart (@SessionValue cart: Cart ?): Cart { //
return cart ?: Cart ()
} @Get ("/cart" )
@SessionValue ("cart" ) //
Cart viewCart (@SessionValue @Nullable Cart cart ) { //
cart ?: new Cart ()
}
The following table summarizes the events:
For this project, you can find a list of releases (with release notes) here:
You can find the source code of this project in this repository: