Micronaut

Lightweight Java framework for microservices and serverless applications. Achieves high efficiency through compile-time DI and AOT compilation.

JavaFrameworkMicroservicesDIAOPGraalVMNative

GitHub Overview

micronaut-projects/micronaut-core

Micronaut Application Framework

Stars6,280
Watchers171
Forks1,118
Created:March 7, 2018
Language:Java
License:Apache License 2.0

Topics

cloudnativegroovyjavakotlinmicroservicesserverless

Star History

micronaut-projects/micronaut-core Star History
Data as of: 8/13/2025, 01:43 AM

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

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);
    }
}