Micronaut
Lightweight Java framework for microservices and serverless applications. Achieves high efficiency through compile-time DI and AOT compilation.
GitHub Overview
Topics
Star History
Framework
Micronaut
Overview
Micronaut is a next-generation JVM framework optimized for microservices and serverless applications.
Details
Micronaut is a modern, JVM-based full-stack framework specifically designed for building modular, easily testable microservices and serverless applications. Developed by Object Computing in 2018, it takes a fundamentally different approach from traditional frameworks.
The key feature is compile-time dependency injection (DI) and aspect-oriented programming (AOP). While traditional frameworks like Spring Boot use reflection at runtime, Micronaut uses annotation processors to generate necessary metadata at compile time. This eliminates the need for reflection processing at runtime, achieving significant performance improvements and reduced memory usage.
Combined with GraalVM Native Image, it can achieve startup times of less than 10ms and operate with a minimum of 64MB memory. Real-world cases show 90% reduction in cold start times and 75% memory reduction in AWS Lambda environments. It supports Java 17+, with development possible in Java, Groovy, and Kotlin. The framework comes with standard cloud-native application features (configuration management, service discovery, distributed tracing) and is optimized for Kubernetes environments.
Pros and Cons
Pros
- Fast startup: 10x faster startup than traditional frameworks due to compile-time DI
- Low memory consumption: Operates from ~80MB without reflection
- GraalVM Native support: Achieves sub-second startup with native images
- Microservices optimized: Built-in features for distributed systems development
- Serverless friendly: High performance on Lambda, Azure Functions, etc.
- Compile-time safety: DI errors caught at compile time
- Multi-language support: Java, Groovy, Kotlin compatibility
- Cloud-native: Kubernetes and Service Mesh ready
Cons
- High learning curve: Even Spring developers need new concepts
- Limited ecosystem: Fewer libraries and tools compared to Spring
- Debugging complexity: Difficult to debug compile-time generated code
- Increased build time: Heavy initial builds due to annotation processing
- New technology: Limited adoption cases and documentation
- Java dependency: Limited to JVM languages only
- Spring Boot migration cost: High effort for existing application migration
Key Links
- Micronaut Official Site
- Micronaut Documentation
- Micronaut GitHub
- Micronaut Launch
- Micronaut Guides
- Micronaut Examples
Code Examples
Hello World (Basic Web Application)
package com.example;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.runtime.Micronaut;
@Controller
public class Application {
@Get("/")
public String index() {
return "Hello Micronaut!";
}
@Get("/api/status")
public String status() {
return "Service is running";
}
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
Dependency Injection (Compile-time DI)
package com.example.service;
import jakarta.inject.Singleton;
// Engine interface
public interface Engine {
String start();
}
// V8 engine implementation
@Singleton
public class V8Engine implements Engine {
@Override
public String start() {
return "V8 Engine started";
}
}
// Vehicle class (constructor injection)
package com.example.model;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class Vehicle {
private final Engine engine;
@Inject
public Vehicle(Engine engine) {
this.engine = engine;
}
public String startVehicle() {
return engine.start();
}
}
// Usage example
package com.example.controller;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import com.example.model.Vehicle;
@Controller("/api/vehicle")
public class VehicleController {
private final Vehicle vehicle;
public VehicleController(Vehicle vehicle) {
this.vehicle = vehicle;
}
@Get("/start")
public String startVehicle() {
return vehicle.startVehicle();
}
}
REST API Development
package com.example.controller;
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpStatus;
import io.micronaut.validation.Validated;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Email;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@Controller("/api/users")
@Validated
public class UserController {
private final Map<Long, User> users = new ConcurrentHashMap<>();
private Long nextId = 1L;
@Get
public List<User> getAllUsers() {
return users.values().stream().toList();
}
@Get("/{id}")
public User getUserById(@PathVariable Long id) {
User user = users.get(id);
if (user == null) {
throw new UserNotFoundException("User not found: " + id);
}
return user;
}
@Post
@Status(HttpStatus.CREATED)
public User createUser(@Body @Valid User user) {
user.setId(nextId++);
users.put(user.getId(), user);
return user;
}
@Put("/{id}")
public User updateUser(@PathVariable Long id, @Body @Valid User updatedUser) {
if (!users.containsKey(id)) {
throw new UserNotFoundException("User not found: " + id);
}
updatedUser.setId(id);
users.put(id, updatedUser);
return updatedUser;
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
if (!users.containsKey(id)) {
throw new UserNotFoundException("User not found: " + id);
}
users.remove(id);
}
}
// User entity class
package com.example.model;
import io.micronaut.core.annotation.Introspected;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
@Introspected
public class User {
private Long id;
@NotBlank
private String name;
@Email
@NotBlank
private String email;
// Constructors
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// Custom exception
package com.example.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
Database Operations (Micronaut Data)
package com.example.entity;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@MappedEntity("products")
public class Product {
@Id
@GeneratedValue
private Long id;
@NotNull
private String name;
private String description;
@NotNull
@Positive
private Double price;
// Constructors
public Product() {}
public Product(String name, String description, Double price) {
this.name = name;
this.description = description;
this.price = price;
}
// Getters and setters omitted for brevity
}
// Repository
package com.example.repository;
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.repository.CrudRepository;
import io.micronaut.data.annotation.Query;
import com.example.entity.Product;
import java.util.List;
@Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
List<Product> findByNameContaining(String name);
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
@Query("SELECT p FROM Product p WHERE p.price > :price")
List<Product> findExpensiveProducts(Double price);
List<Product> findByNameContainingOrderByPriceAsc(String name);
}
// Service
package com.example.service;
import jakarta.inject.Singleton;
import com.example.entity.Product;
import com.example.repository.ProductRepository;
import java.util.List;
import java.util.Optional;
@Singleton
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public List<Product> getAllProducts() {
return (List<Product>) productRepository.findAll();
}
public Optional<Product> getProductById(Long id) {
return productRepository.findById(id);
}
public Product saveProduct(Product product) {
return productRepository.save(product);
}
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
public List<Product> searchProductsByName(String name) {
return productRepository.findByNameContaining(name);
}
public List<Product> getProductsInPriceRange(Double minPrice, Double maxPrice) {
return productRepository.findByPriceBetween(minPrice, maxPrice);
}
}
Reactive Programming
package com.example.controller;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.Single;
import io.reactivex.Observable;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.time.Duration;
@Controller("/api/reactive")
public class ReactiveController {
// RxJava Single
@Get("/single")
public Single<String> getSingle() {
return Single.fromCallable(() -> {
// Simulate heavy processing
Thread.sleep(1000);
return "Single response";
});
}
// RxJava Observable
@Get("/stream")
public Observable<String> getStream() {
return Observable.interval(1, java.util.concurrent.TimeUnit.SECONDS)
.map(i -> "Item " + i)
.take(5);
}
// Reactor Mono
@Get("/mono")
public Mono<String> getMono() {
return Mono.fromCallable(() -> "Mono response")
.delayElement(Duration.ofSeconds(1));
}
// Reactor Flux
@Get("/flux")
public Flux<String> getFlux() {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> "Flux item " + i)
.take(3);
}
}
// Reactive HTTP client
package com.example.client;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;
import reactor.core.publisher.Mono;
@Client("https://api.github.com")
public interface GitHubClient {
@Get("/users/{username}")
Single<String> getUser(String username);
@Get("/users/{username}/repos")
Mono<String> getUserRepos(String username);
}
AOP and Interceptors
package com.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
String description() default "";
}
// Interceptor implementation
package com.example.interceptor;
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.context.annotation.Type;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
@Type(Timed.class)
public class TimedInterceptor implements MethodInterceptor<Object, Object> {
private static final Logger LOG = LoggerFactory.getLogger(TimedInterceptor.class);
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
long start = System.currentTimeMillis();
String description = context.getAnnotation(Timed.class)
.stringValue("description")
.orElse("");
try {
Object result = context.proceed();
long duration = System.currentTimeMillis() - start;
LOG.info("Method {} {} executed in {}ms",
context.getMethodName(), description, duration);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
LOG.error("Method {} {} failed after {}ms",
context.getMethodName(), description, duration, e);
throw e;
}
}
}
// AOP usage example
package com.example.service;
import jakarta.inject.Singleton;
import com.example.annotation.Timed;
@Singleton
public class BusinessService {
@Timed(description = "heavy computation")
public String performHeavyTask() {
try {
Thread.sleep(2000); // Simulate heavy processing
return "Task completed";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Task interrupted", e);
}
}
@Timed
public String quickTask() {
return "Quick task done";
}
}
Testing
package com.example.controller;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest
public class UserControllerTest {
@Inject
@Client("/")
HttpClient client;
@Test
public void testGetAllUsers() {
var response = client.toBlocking().exchange(
HttpRequest.GET("/api/users"),
String.class
);
assertEquals(HttpStatus.OK, response.getStatus());
}
@Test
public void testCreateUser() {
String userJson = """
{
"name": "John Doe",
"email": "[email protected]"
}
""";
var response = client.toBlocking().exchange(
HttpRequest.POST("/api/users", userJson),
String.class
);
assertEquals(HttpStatus.CREATED, response.getStatus());
}
}
// Service test
package com.example.service;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import jakarta.inject.Inject;
import com.example.repository.ProductRepository;
import com.example.entity.Product;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@MicronautTest
public class ProductServiceTest {
@Inject
ProductService productService;
@Mock
ProductRepository productRepository;
@Test
public void testSaveProduct() {
// Given
Product product = new Product("Test Product", "Description", 99.99);
when(productRepository.save(any(Product.class))).thenReturn(product);
// When
Product result = productService.saveProduct(product);
// Then
assertNotNull(result);
assertEquals("Test Product", result.getName());
verify(productRepository).save(product);
}
}