AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Mir Backend Jvm Micronaut

skill-anantbhandarkar-make-it-right-mir-backend-jvm-micronaut · by anantbhandarkar

Make It Right (Micronaut module). Micronaut + Micronaut Data + Micronaut Security + Netty HTTP server reliability footguns specific to this framework stack. Covers: compile-time DI and AOT (no runtime reflection — missing bean is a compile error, not a runtime NPE), bean scope pitfalls (@Singleton default, don't hold request state), blocking the Netty event loop (use @ExecuteOn or reactive types)…

— No reviews yet
0 installs
36 views
0.0% view→install

Install

$ agentstack add skill-anantbhandarkar-make-it-right-mir-backend-jvm-micronaut

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-anantbhandarkar-make-it-right-mir-backend-jvm-micronaut)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mir Backend Jvm Micronaut? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

/mir-backend-jvm-micronaut · Make It Right (Micronaut)

Bottom tier of the chain: mir-backend (generic gates) → mir-backend-jvm (JVM runtime model) → this (Micronaut library mechanics). Run the gates first; load the JVM runtime tier for threading, GC, and container-heap concerns; reach for this at Gate 5 (design mechanics), Gate 6 (implementation), and Gate 7 review. Runtime-level concerns (virtual-thread pinning, pool sizing, GC tuning, -XX:MaxRAMPercentage, ThreadLocal hygiene, JMM visibility) live in mir-backend-jvm — not here.

Stack assumed: Micronaut 4.x · Micronaut Data JPA (Hibernate) or Micronaut Data JDBC · Micronaut Security · Micronaut HTTP Server (Netty) · GraalVM CE / Mandrel for native image.

The Micronaut footguns AI walks into most

1. Compile-time DI — missing bean is a compile error, not a runtime NPE

Micronaut generates all DI glue at compile time via annotation processors (no runtime reflection for injection). This means:

  • A missing or ambiguous bean binding fails the build, not the first request. This is strictly better than Spring's runtime NoSuchBeanDefinitionException — but AI sometimes works around compilation failures by adding @Nullable or suppressing errors instead of fixing the missing binding.
  • Classes used only via reflection (Jackson mixins, custom TypeConverter, serialization targets for native image) are not automatically discovered. Register them explicitly.
// WRONG — assumes runtime classpath scanning will find the bean
@Controller("/orders")
public class OrderController {
    @Inject
    OrderService service; // compile error if OrderService has no @Singleton/@Service
}

// RIGHT — ensure the injected type has a bean-defining annotation
@Singleton
public class OrderService { ... }

// Ambiguous bean: two @Singleton implementations of the same interface
// → compile error "Multiple bean definitions found for type..."
// FIX: qualify with @Named or @Primary
@Singleton
@Named("fast")
public class FastOrderService implements OrderService { ... }

@Inject @Named("fast") OrderService service;

@Factory for third-party types: if you need to inject a type you can't annotate, use @Factory:

@Factory
public class RedisFactory {
    @Singleton
    RedisClient redisClient(RedisConfig cfg) {
        return RedisClient.create(cfg.getUri());
    }
}

2. @Singleton default scope: never store per-request state

All Micronaut beans default to @Singleton — one instance, all threads. This is the same trap as Spring's singleton scope, but Micronaut's lack of a default @RequestScope proxy makes it easier to miss.

// WRONG — tenantId is a shared mutable field; threads stomp on each other
@Singleton
public class TenantContext {
    private String tenantId; // shared across all concurrent requests
    public void set(String t) { this.tenantId = t; }
    public String get() { return tenantId; }
}

// RIGHT — pass tenant context as a method argument, or use a @RequestScope bean
// @RequestScope requires the HttpRequest to be in scope; use carefully
@RequestScope
public class TenantContext {
    private String tenantId;
    public void set(String t) { this.tenantId = t; }
    public String get() { return tenantId; }
}
// Inject into singleton with @Inject Provider ctx;
// so each invocation gets the request-scoped instance, not a singleton

Available scopes: @Singleton, @Prototype (new instance per injection point), @RequestScope (per HTTP request), @ThreadLocal (per thread — correct for pooled-thread use). Default to @Singleton for stateless services; use @Prototype for stateful helpers.

3. Blocking the Netty event loop: @ExecuteOn is mandatory for blocking work

Micronaut's HTTP server runs on Netty's event loop. Like Quarkus reactive, blocking the event loop thread degrades the entire server — one blocking call can stall all concurrent requests on that loop.

// WRONG — controller method runs on the event loop by default; JDBC blocks it
@Get("/report")
public Report getReport() {
    return reportService.buildHeavyReport(); // blocking JDBC on event loop → server stall
}

// RIGHT — @ExecuteOn dispatches to a named thread pool
@Get("/report")
@ExecuteOn(TaskExecutors.IO)    // Micronaut's built-in I/O thread pool
public Report getReport() {
    return reportService.buildHeavyReport(); // runs on IO pool, event loop is free
}

// ALSO RIGHT — return a reactive type; Micronaut keeps the call non-blocking
@Get("/report")
public Single getReport() {
    return reportService.buildHeavyReportAsync(); // reactive, event loop not blocked
}

TaskExecutors.IO is a cached thread pool sized to 2 × CPU_cores by default. For sustained blocking workloads, configure a custom executor via micronaut.executors.io.type=fixed and set nThreads explicitly.

For Kotlin coroutines: mark the controller function suspend — Micronaut detects this and runs it on Dispatchers.IO automatically when the function does blocking I/O. Do not mix runBlocking on the event loop.

4. Micronaut Data transactions: @Transactional and repository scope

Micronaut Data uses compile-time-generated repositories (no runtime proxies). @Transactional is applied via a compile-time AOP interceptor — which means self-invocation bypasses the interceptor, same as Spring.

// WRONG — saveWithNotification calls notify() on this; interceptor not involved
@Singleton
public class OrderService {
    @Transactional
    public void saveWithNotification(Order o) {
        repo.save(o);
        notify(o); // direct call — @Transactional on notify() is not applied
    }

    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void notify(Order o) { ... } // never runs in its own tx
}

// RIGHT — extract to a separate @Singleton bean
@Singleton
public class NotificationService {
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void notify(Order o) { ... }
}

Micronaut Data repositories operate in the transaction of their caller. If you call a repository from outside a @Transactional boundary, each repository call gets its own auto-committed mini-transaction. This is correct for reads but breaks for multi-step writes.

JDBC repositories: Micronaut Data JDBC uses a simpler mapping layer than JPA — no lazy loading, no session, no LazyInitializationException. N+1 is still possible via explicit nested queries in a loop; use @Join on repository finder methods to specify eager joins.

5. Compile-time AOP interceptors: what they can and cannot intercept

Micronaut AOP is compile-time — the framework generates interceptor wrappers at build time. This is fast and native-image-friendly but has hard constraints:

  • Only beans managed by Micronaut's DI container can be intercepted. Objects created with new bypass AOP. @Transactional, @Cacheable, @Retryable all silently do nothing on a new-ed instance.
  • final methods cannot be intercepted (they can't be overridden in the generated subclass).
  • private methods cannot be intercepted for the same reason.
// WRONG — @Cacheable on a final method; compile-time warning, caching silently skipped
@Cacheable("products")
public final Product findProduct(Long id) { ... }

// RIGHT — remove final
@Cacheable("products")
public Product findProduct(Long id) { ... }

// WRONG — calling @Retryable from within the same class
public void upload(File f) {
    sendToS3(f);         // self-call; @Retryable not applied
}

@Retryable
public void sendToS3(File f) { ... }

// RIGHT — inject the bean and call through it, or restructure
@Inject S3Uploader uploader; // separate bean
public void upload(File f) { uploader.sendToS3(f); }

6. GraalVM native image with Micronaut: what Micronaut handles vs. what you must declare

Micronaut generates GraalVM metadata (reflect-config.json, resource-config.json, proxy-config.json) automatically for its own classes and for beans it discovers at compile time. The gaps:

  • Dynamic class loading or reflection outside Micronaut's DI — third-party libraries, JDBC drivers, Jackson polymorphic subtypes — must still be registered.
  • Resources not on the classpath path that are loaded with getClass().getResourceAsStream().
  • Runtime-initialized classes with side effects in static initializers (same trap as Quarkus — see runtime-map JVM notes).
// Annotate third-party reflection targets (Micronaut 4 uses @ReflectiveAccess)
@ReflectiveAccess
public class ThirdPartyDto { ... }

// Or use GraalVM reflect-config.json directly for classes you can't annotate
// src/main/resources/META-INF/native-image/reflect-config.json

Run native-image-agent with your test suite before your first native build to auto-generate the bulk of the config:

java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
     -jar app.jar

Then review and trim the generated config (it over-reports). Commit it alongside the source.

7. Micronaut Security: token validation and route-level authorization

Micronaut Security's @Secured or security rules in SecurityRule beans control access. The same IDOR trap applies as in Spring: role/authentication checks do not confirm object ownership.

// WRONG — checks role, not object ownership
@Get("/invoices/{id}")
@Secured("ROLE_USER")
public Invoice get(Long id) {
    return invoiceRepo.findById(id).orElseThrow();
}

// RIGHT — pull the authenticated identity and verify ownership
@Get("/invoices/{id}")
@Secured("ROLE_USER")
public Invoice get(Long id, Authentication auth) {
    Invoice inv = invoiceRepo.findById(id).orElseThrow();
    if (!inv.getOwnerId().equals(auth.getName())) {
        throw new HttpStatusException(HttpStatus.FORBIDDEN, "Not your invoice");
    }
    return inv;
}

For JWT: Micronaut Security validates the signature and expiry automatically when micronaut.security.token.jwt.signatures.secret.generator.secret (or JWKS URL) is configured. Do not skip token validation by setting micronaut.security.enabled=false in non-dev profiles — AI sometimes does this to "fix" auth issues in staging.

How this slots into the core pipeline

  • Gate 5 (Design): confirm blocking/reactive model per controller method; identify @ExecuteOn boundaries; state transaction scope for multi-step data operations; review bean scopes for per-request state.
  • Gate 6 (Implementation): @ExecuteOn(TaskExecutors.IO) on all blocking controllers; @Transactional at the service layer (not in the controller); no final/private on AOP-intercepted methods; @ReflectiveAccess for third-party reflection targets in native image.
  • Gate 7 (Review): the reliability-reviewer additionally checks items 1–7 here for any Micronaut service; native image builds should be verified with native-image-agent output reviewed before release.

Edit boundary (what belongs here vs. above/below)

Apply the 3-tier placement test before adding anything:

  • True for Go/Node/Python too (idempotency, invariants, gates, observability)? → generic core (mir-backend).
  • True for every JVM framework (thread-pool sizing, virtual-thread pinning, GC tuning, container heap, ThreadLocal hygiene, JMM visibility)? → runtime tier (mir-backend-jvm).
  • A mechanical footgun of this library (compile-time DI binding, @Singleton request-state bleed, @ExecuteOn for Netty, Micronaut Data @Transactional self-invocation, compile-time AOP final/private limits, @ReflectiveAccess for native image, Micronaut Security IDOR)? → here.
  • A different JVM framework (Spring Boot, Quarkus) → its own mir-backend-jvm- module. A different runtime → its own tier. Never widen this one.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.