Getting Started with ActiveJ
ActiveJ is a lightweight, high-performance Java framework designed for building scalable and efficient applications. It provides an alternative to traditional Java frameworks like Spring and Micronaut by focusing on speed, minimalism, and modular architecture. This article will introduce ActiveJ, explain some core components, and demonstrate various use cases.
1. Why Choose ActiveJ?
ActiveJ stands out among Java frameworks due to its high performance, lightweight nature, and modular design. Here’s why developers should consider using it:
- High Performance – ActiveJ is optimized for speed and low-latency processing, making it ideal for high-throughput applications.
- Asynchronous & Non-Blocking – The framework is built around an event-driven model, ensuring efficient resource usage and scalable concurrency.
- Minimalistic & Lightweight – Unlike traditional Java frameworks, ActiveJ has no external dependencies, resulting in faster startup times and reduced memory footprint.
- Modular Architecture – Developers can use only the components they need, avoiding unnecessary bloat.
- Built-in Dependency Injection – ActiveJ includes its own DI framework, eliminating the need for third-party solutions.
- Optimized for Microservices – Its non-blocking HTTP servers and efficient serialization make it a great choice for microservice-based architectures.
By offering these advantages, ActiveJ provides an efficient and scalable alternative to frameworks like Spring and Micronaut.
2. Creating a Simple ActiveJ HTTP Server
ActiveJ makes it easy to build lightweight, high-performance HTTP servers with minimal configuration. Unlike traditional frameworks, ActiveJ provides an efficient non-blocking architecture for handling requests. Below, we create a basic HTTP server that responds with a simple message. The server is built using HttpServerLauncher
, which simplifies HTTP server setup by handling configurations and event loops internally.
Dependencies
01 02 03 04 05 06 07 08 09 10 | < dependency > < groupId >io.activej</ groupId > < artifactId >activej-launchers-http</ artifactId > < version >6.0-rc2</ version > </ dependency > < dependency > < groupId >ch.qos.logback</ groupId > < artifactId >logback-classic</ artifactId > < version >1.3.4</ version > </ dependency > |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | public final class SimpleHttpServer extends HttpServerLauncher { @Provides AsyncServlet createServlet() { return request -> HttpResponse.ok200() .withPlainText( "Welcome to ActiveJ!" ) .toPromise(); } public static void main(String[] args) throws Exception { Launcher serverLauncher = new SimpleHttpServer(); serverLauncher.launch(args); } } |
The SimpleHttpServer
class extends HttpServerLauncher
, which includes built-in configurations for running an HTTP server. The @Provides
annotation designates the createServlet()
method as a provider of an AsyncServlet
to handle HTTP requests. When accessed, the servlet returns the text message "Welcome to ActiveJ!"
. The main()
method starts the server using Launcher
, which manages the application’s lifecycle.
When you run the application, the ActiveJ HTTP server starts, and you can access it in a web browser or via a tool like curl
. Visiting http://localhost:8080
will return a plain text response:
This confirms that the server is running successfully and handling HTTP requests as expected.
3. Using ActiveJ Serializer for High-Performance Object Serialization
Serialization is a crucial process in modern applications, allowing objects to be converted into a byte stream for storage or transmission. ActiveJ Serializer provides an efficient way to serialize and deserialize objects with minimal overhead.
Dependencies:
1 2 3 4 5 | < dependency > < groupId >io.activej</ groupId > < artifactId >activej-serializer</ artifactId > < version >6.0-rc2</ version > </ dependency > |
The following example demonstrates how to serialize and deserialize a Person
object using ActiveJ Serializer.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Person { @Serialize public final int age; @Serialize public final String name; private String surname; public Person( @Deserialize ( "age" ) int age, @Deserialize ( "name" ) String name) { this .age = age; this .name = name; } @Serialize public String getSurname() { return surname; } public void setSurname(String surname) { this .surname = surname; } } |
The Person
class above is designed for serialization using ActiveJ Serializer. The @Serialize
annotation marks the age
and name
fields for automatic serialization, ensuring they are included when an object of this class is serialized. The constructor, annotated with @Deserialize
, specifies how objects should be deserialized by mapping parameters (age
and name
) to their respective fields.
The surname
field is private and not serialized directly, but the getSurname()
method is annotated with @Serialize
, allowing it to be included in the serialization process.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class SerializationExample { public static void main(String[] args) { // Create Binary Serializer for the Person class BinarySerializer serializer = SerializerFactory.defaultInstance().create(Person. class ); // Create and Serialize a Person Object Person john = new Person( 30 , "Thomas" ); john.setSurname( "Pink" ); // Serialize Person Object into a Pre-allocated Buffer byte [] buffer = new byte [ 200 ]; // Ensure the buffer is large enough serializer.encode(buffer, 0 , john); // Deserialize Back to Person Object Person deserializedPerson = serializer.decode(buffer, 0 ); System.out.println( "Deserialized Person: Name = " + deserializedPerson.name + ", Age = " + deserializedPerson.age + ", Surname = " + deserializedPerson.getSurname()); } } |
This example first creates a binary serializer for the Person
class using SerializerFactory.defaultInstance().create(Person.class)
. A new Person
object named Thomas with a surname Pink is then instantiated and serialized into a byte buffer (byte[] buffer = new byte[200]
). The serializer.encode()
method encodes the object into the buffer, and serializer.decode()
reconstructs the original object from it.
When the program runs, it serializes the Person
object with the name Thomas, age 30, and surname Pink into a byte array. It then deserializes the object back and prints the values to the console. The expected output will be:
1 | Deserialized Person: Name = Mr Fish, Age = 30, Surname = Smith |
This output confirms that the ActiveJ Serializer correctly encodes the object into a binary format and accurately reconstructs it during deserialization. The surname
field, which is privately stored, is also included in serialization through the getter method annotated with @Serialize
.
4. Working with Dependency Injection in ActiveJ
Dependency Injection (DI) allows for better code organization, maintainability, and testability by managing dependencies in a structured way. ActiveJ Inject is a lightweight DI framework that avoids reflection, making it faster than traditional solutions like Spring or Guice. The example below demonstrates how ActiveJ Inject can be used to inject dependencies in a simple application that simulates a notification service.
Dependencies
1 2 3 4 5 | < dependency > < groupId >io.activej</ groupId > < artifactId >activej-inject</ artifactId > < version >6.0-rc2</ version > </ dependency > |
4.1 Example: Notification System Using ActiveJ Inject
The following example demonstrates how to use ActiveJ Inject to build a simple notification system where a service sends email notifications to users. This setup efficiently manages dependencies while keeping the code clean and modular.
EmailService – Sends email notifications
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | public final class EmailService { private final String smtpServer; @Inject public EmailService(String smtpServer) { this .smtpServer = smtpServer; } public void sendEmail(String recipient, String message) { System.out.printf( "Sending email to %s via %s at %s: %s%n" , recipient, smtpServer, LocalDateTime.now(), message); } } |
The EmailService
class is a simple email notification service that relies on dependency injection for configuration. The smtpServer
field stores the SMTP server address, which is injected via the constructor using the @Inject
annotation. The sendEmail
method takes a recipient and a message, then simulates sending an email by printing the details, including the recipient, SMTP server, current timestamp, and message content. This approach ensures flexibility, as different SMTP servers can be injected without modifying the class.
NotificationService – Uses EmailService to send notifications
01 02 03 04 05 06 07 08 09 10 11 12 13 | public final class NotificationService { private final EmailService emailService; @Inject public NotificationService(EmailService emailService) { this .emailService = emailService; } public void notifyUser(String user, String message) { emailService.sendEmail(user, message); } } |
The NotificationService
class acts as a higher-level service that depends on EmailService
to send notifications. Using dependency injection, the EmailService
instance is passed through the constructor, ensuring flexibility. The notifyUser
method takes a user and a message, delegating the task to emailService.sendEmail()
, which handles the actual email sending.
Configuration module – Provides SMTP server details
1 2 3 4 5 6 7 | public class ConfigModule extends AbstractModule { @Provides String smtpServer() { return "smtp.example.com" ; } } |
The ConfigModule
class extends AbstractModule
and defines a configuration provider for the SMTP server address. Using the @Provides
annotation, the smtpServer()
method returns a predefined SMTP server string ("smtp.example.com"
), which can be injected into other classes that require it, such as EmailService
.
Service module – Provides NotificationService dependency
1 2 3 4 5 6 7 | public class ServiceModule extends AbstractModule { @Provides NotificationService notificationService(EmailService emailService) { return new NotificationService(emailService); } } |
The ServiceModule
class extends AbstractModule
and provides an instance of NotificationService
using dependency injection. The @Provides
annotation marks the notificationService
method as a provider method, which takes an EmailService
instance as a parameter and returns a new NotificationService
object. This ensures that whenever NotificationService
is required, an instance with the necessary dependency (EmailService
) is created and managed by the dependency injection framework promoting modularity.
Main method to launch the application
1 2 3 4 5 6 7 8 9 | public class DIExample { public static void main(String[] args) { Injector injector = Injector.of( new ConfigModule(), new ServiceModule()); NotificationService notificationService = injector.getInstance(NotificationService. class ); notificationService.notifyUser( "user@jcg.com" , "Your order has been shipped!" ); } } |
In the main
method, an Injector
is created with ConfigModule
and ServiceModule
, which provide the necessary dependencies. The Injector
is then used to retrieve an instance of NotificationService
, which is automatically constructed with its required dependencies. Finally, the notifyUser
method is called to send an email notification, showcasing how dependencies are managed and injected by ActiveJ’s DI framework.
Output
1 | Sending email to user@jcg.com via smtp.example.com at 2025-03-18T17:50:37.532544: Your order has been shipped! |
This shows the notification service successfully sending an email using the injected SMTP server configuration.
5. Async I/O in ActiveJ
ActiveJ provides powerful asynchronous I/O capabilities that allow applications to handle high-throughput data processing efficiently. By leveraging the event-driven architecture of ActiveJ’s Eventloop and the use of Promises, we can build non-blocking, scalable applications that process multiple tasks concurrently without creating excessive threads. Promises in ActiveJ enable the handling of asynchronous operations, making it easier to work with I/O-bound tasks such as networking, file operations, and database queries.
5.1 Event Loop
The ActiveJ Eventloop efficiently processes code asynchronously within event-driven loops and dedicated threads, enabling high-performance, non-blocking operations. Here’s an example demonstrating Eventloop in ActiveJ:
01 02 03 04 05 06 07 08 09 10 | public final class EventloopExample { public static void main(String[] args) { Eventloop eventloop = Eventloop.create(); eventloop.post(() -> System.out.println( "Executing an async task in Eventloop" )); eventloop.run(); } } |
In this example, we create an Eventloop instance and post a simple task that prints a message. The run()
method processes the task asynchronously, demonstrating ActiveJ’s event-driven model.
5.2 Scheduling Tasks in ActiveJ Eventloop
ActiveJ’s Eventloop provides a mechanism for scheduling tasks with precise timing control. By using the delay()
method, we can schedule tasks to execute after a specific delay, making it useful for time-sensitive operations such as retries, background processing, or scheduled event handling. The event loop processes these tasks asynchronously in a single-threaded model, ensuring efficient execution without blocking the main thread.
The following example demonstrates how tasks can be scheduled with different delays and how they execute in the expected order.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | public final class EventloopSchedulerExample { public static void main(String[] args) { Eventloop eventloop = Eventloop.builder() .withCurrentThread() .build(); long startTime = currentTimeMillis(); // Scheduling tasks with different delays eventloop.delay(2500L, () -> System.out.println( "Task with 2500ms delay executed at: " + (currentTimeMillis() - startTime) + "ms" )); eventloop.delay(800L, () -> System.out.println( "Task with 800ms delay executed at: " + (currentTimeMillis() - startTime) + "ms" )); eventloop.delay(200L, () -> System.out.println( "Task with 200ms delay executed at: " + (currentTimeMillis() - startTime) + "ms" )); // Initial log before running the event loop System.out.println( "Starting event loop, current time: " + (currentTimeMillis() - startTime) + "ms" ); // Running the event loop to process scheduled tasks eventloop.run(); } } |
This example initializes an Eventloop
, schedules three tasks with different delays, and then runs the event loop.
Output
1 2 3 4 5 | Starting event loop, current time: 38ms Task with 200ms delay executed at: 211ms Task with 800ms delay executed at: 807ms Task with 2500ms delay executed at: 2501ms 19:16:50.853 [main] INFO io.activej.eventloop.Eventloop - Eventloop{loop=4} finished |
The output illustrates how ActiveJ’s Eventloop efficiently schedules and executes tasks asynchronously according to their specified delays. Tasks are processed in ascending order of delay, ensuring that the shortest delay executes first and the longest executes last. The timestamps validate that each task runs at the expected interval from when the event loop started.
5.3 Promises
In ActiveJ’s asynchronous programming model, Promises serve as fundamental components. A Promise represents the outcome of an operation that is still in progress, similar to Java Futures. Here’s see how Promises work in ActiveJ.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | public final class PromiseExample { public static void main(String[] args) { Eventloop eventloop = Eventloop.create(); eventloop.execute(() -> { System.out.println( "Starting the promise chain..." ); // Create an immediate promise with a value Promise<String> result = Promise.of( "Hello, ActiveJ!" ); // Attach a callback when the result is ready result.whenResult(res -> System.out.println( "The first result is " + res)); // Create a delayed promise that resolves after 10 seconds Promises.delay(Duration.ofSeconds( 10 ), Promise.of( "Delayed result" )) .whenResult(res -> System.out.println( "The delayed result is " + res)); }); eventloop.run(); } } |
This code starts by creating an Eventloop
instance, which schedules and executes asynchronous tasks. Within the event loop, it first prints a message indicating the start of the promise chain. Then, it creates an immediate promise with the value "Hello, ActiveJ!"
and attaches a callback using whenResult()
, which prints the result once it is available.
Next, it schedules a delayed promise using Promises.delay()
, which resolves after 10 seconds with the value "Delayed result"
and prints it when completed. Finally, the event loop is started using eventloop.run()
, ensuring all scheduled tasks execute in order.
Output
1 2 3 4 | Starting the promise chain... The first result is Hello, ActiveJ! The delayed result is Delayed result 19:42:54.630 [main] INFO io.activej.eventloop.Eventloop - Eventloop{loop=10} finished |
5.3.1 Example Asynchronous Employee Data Retrieval with ActiveJ Promises
In the following example, we demonstrate how to use Promises with an Employee
class to simulate asynchronous data retrieval and processing.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public final class Employee { private final String name; private final int id; public Employee( int id, String name) { this .id = id; this .name = name; } public String getName() { return name; } public int getId() { return id; } public static Promise<Employee> fetchEmployee() { return Promise.of( new Employee( 101 , "Alice" )); } public static Promise<String> fetchEmployeeDetails(Employee employee) { return Promises.delay(Duration.ofSeconds( 5 ), Promise.of( "Employee ID: " + employee.getId() + ", Name: " + employee.getName())); } public static void main(String[] args) { Eventloop eventloop = Eventloop.create(); eventloop.execute(() -> { System.out.println( "Fetching Employee..." ); // Fetch Employee immediately fetchEmployee().whenResult(employee -> { System.out.println( "Employee fetched: " + employee.getName()); // Fetch Employee details after a delay fetchEmployeeDetails(employee) .whenResult(details -> System.out.println( "Employee Details: " + details)); }); }); eventloop.run(); } } |
The Employee
class represents an employee with an ID and name, while fetchEmployee()
returns an immediate promise of an Employee
object. The fetchEmployeeDetails()
method simulates a 5-second delay before returning the employee’s details using Promises.delay()
. During execution, the program first fetches an employee immediately and then asynchronously retrieves and prints the employee details after a 5-second delay.
Output
1 2 3 4 | Fetching Employee... Employee fetched: Alice Employee Details: Employee ID: 101, Name: Alice 11:47:33.611 [main] INFO io.activej.eventloop.Eventloop - Eventloop{loop=6} finished |
6. Conclusion
In this article, we explored ActiveJ’s high-performance event-driven architecture, covering Eventloop
for task scheduling, Promise
for asynchronous computations, dependency injection with ActiveJ Inject, and efficient object serialization using ActiveJ Serializer. Through practical examples, we demonstrated how ActiveJ simplifies concurrency, I/O operations, and data serialization, making it a powerful choice for building scalable and responsive applications.
7. Download the Source Code
This article provided an introduction to ActiveJ.
You can download the full source code of this example here: Introduction to activeJ