Technical Documentation: micronaut-projects/micronaut-core
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (`README.md`)
Micronaut Framework
Micronaut Framework is a modern, JVM-based, full stack Java framework designed for building modular, easily testable JVM applications with support for Java, Kotlin and the Groovy language.
The Micronaut framework was originally created by a team who had also worked on the Grails framework. The Micronaut framework takes inspiration from lessons learned over the years building real-world applications from monoliths to microservices using Spring, Spring Boot and the Grails framework. The core team continues to develop and maintain the Micronaut project through the support of the Micronaut Foundation.
Micronaut Framework aims to provide all the tools necessary to build JVM applications including:
- Dependency Injection and Inversion of Control (IoC)
- Aspect Oriented Programming (AOP)
- Sensible Defaults and Auto-Configuration
With Micronaut Framework you can build Message-Driven Applications, Command Line Applications, HTTP Servers and more whilst for Microservices in particular Micronaut Framework also provides:
- Distributed Configuration
- Service Discovery
- HTTP Routing
- Client-Side Load Balancing
At the same time Micronaut Framework aims to avoid the downsides of frameworks like Spring, Spring Boot and Grails by providing:
- Fast startup time
- Reduced memory footprint
- Minimal use of reflection
- Minimal use of proxies
- No runtime bytecode generation
- Easy Unit Testing
This is achieved by pre-computing the framework infrastructure at compilation time which reduces the logic required at runtime for the application to work.
For more information on using Micronaut Framework see the documentation at micronaut.io
Example Applications
Example Micronaut applications can be found in guides.micronaut.io
Building From Source
To build from source checkout the code and run:
./gradlew publishToMavenLocalTo build the documentation run ./gradlew docs. The documentation is built to build/docs/index.html.
Contributing Code
If you wish to contribute to the development of Micronaut Framework please read the CONTRIBUTING.md
Versioning
Micronaut Framework uses Semantic Versioning 2.0.0. To understand what that means, please see the specification documentation. Exclusions to Micronaut Framework's public API include any classes annotated with @Experimental or @Internal, which reside in the io.micronaut.core.annotation package.
Overview (`aop/README.md`)
Micronaut AOP
This module provides support for Aspect-Oriented Programming in Micronaut.
Overview (`buffer-netty/README.md`)
Micronaut Buffer Netty
This module provides an implementation of the ByteBuffer abstraction over Netty.
Overview (`context/README.md`)
Micronaut Context
This module extends the micronaut-inject module with addition bean container services such as:
- Job Scheduling with
@Scheduled - Event Listeners with
@EventListener - Immutable Configuration Properties
Overview (`core-bom/README.md`)
Micronaut BOM
This module produces the Micronaut Bill of Materials (BOM). New Entries can be added in the root build.gradle.
Overview (`core-reactive/README.md`)
Micronaut Core Reactive
This module provides core integration with Reactive Streams.
Overview (`core/README.md`)
Micronaut Core
Common utility classes and interfaces.
Overview (`function-client/README.md`)
Micronaut Function Client
This module provides abstractions over invoking Serverless Functions.
Overview (`function/README.md`)
Micronaut Function
This module provides abstractions of Serverless functions.
Overview (`graal/README.md`)
Micronaut GraalVM
This module provides additional code generation facilities for producing GraalVM configuration.
Overview (`http-client-core/README.md`)
Micronaut HTTP Client
This module provides a Reactive HTTP client based on the Micronaut and Reactive streams APIs.
Overview (`http-client/README.md`)
Micronaut HTTP Client
This module provides an implementation of the Micronaut HTTP Client interfaces for RxJava 2.
Overview (`inject/README.md`)
Micronaut Inject
Core dependency injection and Inversion of Control (IoC) API classes.
Overview (`runtime-osx/README.md`)
runtime-osx
This subproject contains extensions specifically for Mac OS X.
Overview (`test-suite-crema-graalvm/README.md`)
Crema service loading tests
Tests of io.micronaut.core.io.service in a native image built with runtime class loading
(-H:+RuntimeClassLoading, Crema).
Crema is experimental, and what it supports changes between GraalVM releases. The native tests are
therefore opt-in. Without the cremaTests Gradle property this module has no nativeTest task, so the
GraalVM workflows, which run every nativeTest task of the build, do not run it.
The Crema CI workflow, .github/workflows/crema.yml, runs them with the property on the Oracle GraalVM
release named below. It runs on pull requests that change the package io.micronaut.core.io.service (its
sources, or its tests in micronaut-core and inject-java), NativeImageUtils, the native-image
configuration of micronaut-core or this module. It also runs every week, and on demand, where another
GraalVM can be chosen. When the tests are verified with another release, change GRAALVM_JAVA_VERSION in
that workflow too.
| Command | What it runs |
|---|---|
./gradlew :test-suite-crema-graalvm:test |
The tests on the JVM, where there is no service table. Part of the regular build. |
GRAALVM_HOME=<GraalVM> ./gradlew :test-suite-crema-graalvm:nativeTest -PcremaTests=true |
The tests in a Crema image. |
Setting the ORG_GRADLE_PROJECT_cremaTests=true environment variable is the same as passing-PcremaTests=true.
Last verified with Oracle GraalVM 25.0.3 on macOS arm64: 4 tests pass andclassLoadedAtRunTimeRunsInImageCodeAndGetsTheStaticServiceTable is skipped, because Crema in that
release defines classes at run time but does not run their static initializers. It also cannot define a
class that has an InnerClasses attribute, which is why the cremaRuntime sources use no lambdas and no
nested classes, and are compiled with -XDstringConcat=inline.
Overview (`test-suite-jdt/README.md`)
test-suite-jdt
A test suite that compiles with the Eclipse JDT compiler (ECJ) instead of javac, so that the
Micronaut annotation processors are exercised against JDT's javax.lang.model implementation.
Both compilers are standards compliant, but the specification deliberately leaves things open β
most notably the order of TypeElement.getEnclosedElements() and the shape of compiler synthesised
members β and processors that assume javac's behaviour produce wrong metadata under JDT. See
micronaut-core#12658 for an
example of a bug that only reproduced when building from Eclipse.
Layout
| Path | What it does |
|---|---|
src/main/java |
JdtParser, a JavaParser that compiles in memory with ECJ, plus a class loader over the result |
src/main/groovy |
AbstractJdtTypeElementSpec and AbstractCompilerParitySpec, the two base classes the tests use |
src/test/groovy |
The specs: differential (javac vs JDT) comparisons and JDT-only runtime tests |
src/jdt/java |
Beans compiled by the ECJ batch compiler through the compileJdt Gradle task |
src/test/java |
JUnit tests that run the beans in src/jdt/java inside a real ApplicationContext |
The two kinds of test
Differential (AbstractCompilerParitySpec) compiles the same sources twice, once with javac and
once with ECJ, then renders every generated bean definition, introspection and metadata resource as
a stable string and asserts the two are identical. Because javac is the reference implementation
that Micronaut is developed against, any difference is a Micronaut bug rather than a compiler bug.
Adding coverage is just adding a source snippet.
Two things are normalised before comparing, because they are not properties of the generated
metadata: the order in which entries contributed by different classes appear in a metadata JSON
file (that follows the compiler's visit order), and the index in the name of a generated evaluated
expression class (that comes from a counter that is static for the JVM).
Runtime (AbstractJdtTypeElementSpec and JdtCompiledBeansTest) compiles only with JDT and then
starts an ApplicationContext over the result, so that the metadata is proven to work and not only
to match.
Running
./gradlew :test-suite-jdt:test.Agents Skills Docs SKILL (`.agents/skills/docs/SKILL.md`)
name: docs license: Apache-2.0 compatibility: Micronaut framework repositories in micronaut-projects generated from micronaut-project-template metadata: author: Γlvaro SΓ‘nchez-Mariscal version: "1.0.0"
Docs (Micronaut Maintainer)
Use this skill for maintainer-facing guide work in Micronaut framework repositories. Do not default to end-user application documentation advice.
Goal
Implement source-backed documentation changes in src/main/docs/guide, keep toc.yml and content in sync, and validate with the Gradle docs pipeline used across micronaut-projects modules.
Procedure
- Confirm repository docs layout and build tasks.
- Plan
toc.ymland.adocupdates in lockstep. - Apply Micronaut docs macro conventions used by maintainers.
- Prefer generated configuration property references over manual tables.
- Build and validate
publishGuideanddocsoutputs.
1) Confirm docs layout and build tasks
- Check
src/main/docs/guide/toc.ymlfirst. - List relevant guide files under
src/main/docs/guide/**/*.adoc. - If examples are involved, inspect
doc-examples/(if present) and plan snippet tags from executable sources. - If assets are involved, keep images in
src/main/docs/resources/img/. - Confirm standard commands from
CONTRIBUTING.md:./gradlew publishGuide(or./gradlew pG) for guide assembly../gradlew docsfor guide + API docs assembly.
- If repository layout diverges from template conventions, follow local conventions and explicitly report the divergence.
2) Keep `toc.yml` and files in lockstep
Treat src/main/docs/guide/toc.yml as navigation source of truth.
Rules:
- Every visible section must exist in
toc.yml. - Every
toc.ymlentry must resolve to a real.adocpath. - Preserve section order unless the request explicitly changes navigation.
- Use nested sections for grouped topics when needed.
Example nested pattern:
controlPanels:
title: Available Control Panels
builtIn: Built-in
management: ManagementThis maps to:
src/main/docs/guide/controlPanels.adocsrc/main/docs/guide/controlPanels/builtIn.adocsrc/main/docs/guide/controlPanels/management.adoc
3) Apply Micronaut docs macro conventions
Use docs macros registered by micronaut-build (DocsExtensionRegistry) and maintained for framework guides.
Preferred mapping:
| Need | Preferred pattern |
|---|---|
| Dependency instructions | dependency:group:artifact[scope=...] |
| Source sample synchronized with test suites | snippet::path/to/File.ext[tags=...] |
| Multi-format configuration snippets | [configuration] listing blocks |
| Configuration properties reference | include::{includedir}configurationProperties/<fqcn>.adoc[] |
| Shell commands | [source,bash] blocks |
| Repository/release links | https://github.com/{githubSlug} and /releases links |
Guardrails:
- Do not handwrite separate Maven/Gradle dependency blocks when
dependency:is suitable. - Do not replace Micronaut
snippet::macros with direct Asciidoctor[source]includes or pasted code blocks just because the current examples have one language. The macro is the multi-language, source-backed contract for generated module docs. - Do not remove snippet callouts when touching examples. Preserve existing callout tags and callout explanations unless the underlying executable source no longer has that behavior.
- Do not duplicate configuration property tables manually when generated references exist.
- Keep environment-sensitive instructions explicit (for example
MICRONAUT_ENVIRONMENTS=dev). - Prefer stable links to official Micronaut docs for endpoint semantics.
4) Use generated configuration property references correctly
micronaut-docsprovidesAsciiDocPropertyReferenceWriter, which generates AsciiDoc property fragments from configuration metadata.- Micronaut docs build wiring consumes those fragments via
{includedir}configurationProperties/...includes. - Prefer includes over hand-maintained property tables.
See references/micronaut-docs-providers.md for confirmed provider/macro details and source locations.
5) Build and validate documentation
From repository root, run:
./gradlew publishGuide
./gradlew docsValidation checklist:
- Commands exit with code
0. - Output exists under
build/docs/. - Updated sections appear in navigation and render without missing includes.
- Added images/links resolve.
If publishGuide passes but docs fails, fix the failing stage and rerun both commands.
Maintainer Delivery Contract
When finishing docs work, report:
- Exact changed files (
.adoc,toc.yml, docs resources). - Which conventions were applied (
dependency:,snippet::,[configuration], generated property includes). - Build commands run and outcomes.
- Any repo-specific divergences or follow-ups.
Validation Checklist
-
toc.ymland.adocchanges are consistent. - Macros and includes follow Micronaut maintainer conventions.
- Existing
snippet::macros and callouts are preserved unless executable source changes require a different tagged snippet. -
publishGuideanddocsexecuted successfully. - Output and navigation verified under
build/docs/. - Guidance remains maintainer-focused (not generic app docs).
References
references/micronaut-docs-providers.mdreferences/control-panel-patterns.mdCONTRIBUTING.mdsrc/main/docs/guide/toc.yml
.Agents Skills Docs References Control Panel Patterns (`.agents/skills/docs/references/control-panel-patterns.md`)
Micronaut Control Panel Guide Patterns
This reference captures practical guide-authoring conventions observed in micronaut-projects/micronaut-control-panel and applicable to other Micronaut framework modules.
Structure patterns
Source files:
src/main/docs/guide/toc.ymlsrc/main/docs/guide/quickStart.adocsrc/main/docs/guide/controlPanels.adoc
Observed conventions:
toc.ymldefines order and nesting.- Nested sections map to folders/files under
src/main/docs/guide/. - Top-level section pages are concise and delegate details to subpages.
`toc.yml` nested section pattern
Observed pattern:
controlPanels:
title: Available Control Panels
builtIn: Built-in
management: ManagementMapping:
src/main/docs/guide/controlPanels.adocsrc/main/docs/guide/controlPanels/builtIn.adocsrc/main/docs/guide/controlPanels/management.adoc
Content patterns in `.adoc`
Dependency instructions
Use dependency: macro with scope where relevant:
dependency:io.micronaut.controlpanel:micronaut-control-panel-ui[scope=developmentOnly]Configuration properties includes
Use generated include fragments:
include::{includedir}configurationProperties/io.micronaut.controlpanel.core.config.ControlPanelModuleConfiguration.adoc[]Runtime environment commands
Use explicit environment examples in [source,bash] blocks:
MICRONAUT_ENVIRONMENTS=dev ./gradlew runEndpoint links and warnings
- Prefer links to official
docs.micronaut.ioendpoint sections. - Use admonitions for critical requirements (
WARNING:).
Build workflow alignment
Contributing workflow in control-panel matches template repos:
./gradlew publishGuide(or./gradlew pG) for guide output../gradlew docsfor guide + Javadocs.
Example-source strategy
- Keep runnable examples in
doc-examples/when the repository contains that project. - Prefer including examples via Micronaut docs macros/snippet workflows so docs stay aligned with tested code.
- Avoid large hand-written code blocks in guide pages when the same content can be sourced from executable examples.
Maintainer takeaways
- Keep
toc.ymland guide files synchronized at all times. - Favor macro/includes-backed content to reduce drift.
- Keep examples runnable and environment-explicit.
- Validate docs output before finalizing changes.
.Agents Skills Docs References Micronaut Docs Providers (`.agents/skills/docs/references/micronaut-docs-providers.md`)
Micronaut Docs Providers and Guide Macros
This reference captures source-backed behavior relevant to framework maintainers writing guide documentation.
1) Configuration property writer from `micronaut-docs`
micronaut-docs provides a metadata writer that emits AsciiDoc configuration property fragments.
Source evidence:
docs-asciidoc-config-props/src/main/java/io/micronaut/documentation/asciidoc/AsciiDocPropertyReferenceWriter.javadocs-asciidoc-config-props/src/main/resources/META-INF/services/io.micronaut.inject.configuration.ConfigurationMetadataWriter
Behavior:
- Writes
META-INF/config-properties.adocfrom Micronaut configuration metadata. - Produces property table content (property, type, description, default value).
Maintainer implication:
- Prefer
include::{includedir}configurationProperties/<fqcn>.adoc[]in guides instead of manually writing property tables.
2) Asciidoctor extension registry used in module docs builds
Guide macros used by Micronaut module docs are registered by micronaut-build.
Source evidence:
micronaut-build/micronaut-gradle-plugins/src/main/groovy/io/micronaut/docs/DocsExtensionRegistry.groovymicronaut-build/micronaut-gradle-plugins/src/main/resources/META-INF/services/org.asciidoctor.jruby.extension.spi.ExtensionRegistry
Registered extensions include:
- Inline macros:
api,mnapi,ann,pkg,jdk,jee,rs,rx,reactor,dependency - Block macro:
snippet - Block processor:
configuration
3) `dependency:` macro
Source:
micronaut-build/micronaut-gradle-plugins/src/main/groovy/io/micronaut/docs/BuildDependencyMacro.groovy
Usage:
dependency:io.micronaut.controlpanel:micronaut-control-panel-ui[scope=developmentOnly]
Why maintainers should prefer it:
- Keeps dependency snippets consistent across Gradle/Maven output.
- Avoids drift from hand-written dual build-tool snippets.
4) `snippet::` macro
Source:
micronaut-build/micronaut-gradle-plugins/src/main/groovy/io/micronaut/docs/LanguageSnippetMacro.groovy
Maintainer guidance:
- Use for examples that should stay aligned with source/test files.
- Prefer tagged snippets for stable, focused excerpts.
- Preserve
snippet::macros even when a repository currently has only one language underdoc-examples/. The macro is still the docs contract for language-aware rendering and lets the same guide continue to support future Java, Kotlin, or Groovy examples without rewriting the guide. - Preserve callouts attached to snippet excerpts. Callouts are part of the tutorial explanation, not disposable rendering decoration; remove them only when the referenced executable source behavior is removed or the callout is replaced with an equivalent source-backed explanation.
- Do not convert snippet output to raw
[source,<language>]blocks,include::of generated source, or pasted code as a cleanup shortcut.
5) `[configuration]` block processor
Source:
micronaut-build/micronaut-gradle-plugins/src/main/groovy/io/micronaut/docs/ConfigurationPropertiesMacro.groovy
Maintainer guidance:
- Use for multi-format configuration examples rather than one-off single-format blocks.
6) Docs task wiring in `micronaut-build`
Source:
micronaut-build/micronaut-gradle-plugins/src/main/groovy/io/micronaut/build/MicronautDocsPlugin.groovy
Relevant properties and behavior:
- Guide tasks include
publishGuide*,assembleDocs, anddocs. - Asciidoctor properties include
includedir,micronautapi,source-highlighter, and language handling. - Built docs are assembled into
build/docs/.
Practical rules
- Prefer macro-driven docs (
dependency:,snippet::,[configuration]) over hand-maintained formatting. - Prefer generated config property includes over manually authored property tables.
- Validate with
./gradlew publishGuideand./gradlew docsbefore completion.
Src Main Docs Guide Aop (`src/main/docs/guide/aop.adoc`)
Aspect-Oriented Programming (AOP) has historically had many incarnations and some very complicated implementations. Generally AOP can be thought of as a way to define cross-cutting concerns (logging, transactions, tracing, etc.) separate from application code in the form of aspects that define advice.
There are typically two forms of advice:
- Around Advice - decorates a method or class
- Introduction Advice - introduces new behaviour to a class.
In modern Java applications, declaring advice typically takes the form of an annotation. The most well-known annotation advice in the Java world is probably @Transactional, which demarcates transaction boundaries in Spring and Grails applications.
The disadvantage of traditional approaches to AOP is the heavy reliance on runtime proxy creation and reflection, which slows application performance, makes debugging harder and increases memory consumption.
Micronaut framework tries to address these concerns by providing a simple compile-time AOP API that does not use reflection.
Src Main Docs Guide Certificates (`src/main/docs/guide/certificates.adoc`)
[[certificates]]
== Certificate providers
Certificate providers let you externalize how TLS key and trust material is supplied to the framework. Each provider is a named bean and can be referenced from server and client SSL configuration using:
micronaut.server.ssl.key-name/micronaut.server.ssl.trust-namemicronaut.http.client.ssl.key-name/micronaut.http.client.ssl.trust-name
Micronaut ships three provider types:
- File-based: load from files on disk with optional auto-reload
- Resource-based: load from Micronaut resources (classpath:, file:, string:, base64:)
- Self-signed: generate ephemeral certificates (testing/dev)
== File-based provider (PEM with separate key and cert, auto-refresh)
The file provider loads a private key and certificate chain from disk and can automatically reload when files change.
[configuration]
micronaut:
ssl:
enabled: true
server:
ssl:
# Use the named provider "cert-a" for the server's key/cert
key-name: cert-a
key:
password: ""
http:
client:
ssl:
# Trust the same material for the client
trust-name: cert-a
certificate:
file:
cert-a:
format: pem
path: /path/to/server.key.pem
certificate-path: /path/to/server.crt.pem
== Resource-based provider (inline PEM and cert-only trust store)
The resource provider loads the material from any Micronaut resource location.
[configuration]
micronaut:
ssl:
enabled: true
server:
ssl:
key-name: cert-a
http:
client:
ssl:
trust-name: trust-a
certificate:
resource:
cert-a:
resource: "string:-----BEGIN PRIVATE KEY-----\n...base64...\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n...base64...\n-----END CERTIFICATE-----\n"
trust-a:
resource: "string:-----BEGIN CERTIFICATE-----\n...base64...\n-----END CERTIFICATE-----\n"
For JKS/PKCS12, you can embed binary content via base64:.... For example: resource: "base64:" and set password if required.
== Self-signed provider
The self-signed provider generates temporary certificates. It requires the netty-pkitesting dependency:
dependency:io.netty:netty-pkitesting[scope="runtimeOnly"]
[configuration]
micronaut:
ssl:
enabled: true
server:
ssl:
key-name: cert-server
trust-name: cert-client
client-authentication: need
http:
client:
ssl:
key-name: cert-client
trust-name: cert-server
certificate:
self-signed:
cert-server: {}
# Second provider with a custom subject
cert-client:
subject: "CN=foo"
For per-listener SSL in Netty server, you can also set key-name and trust-name under micronaut.server.netty.listeners.<listener>.ssl.
Src Main Docs Guide Cli (`src/main/docs/guide/cli.adoc`)
The Micronaut CLI is the recommended way to create new Micronaut projects. The CLI includes commands for generating specific categories of projects, allowing you to choose between build tools, test frameworks, and even pick the language to use in your application. The CLI also provides commands for generating artifacts such as controllers, client interfaces, and serverless functions.
TIP: We have a website that can be used to generate projects instead of the CLI. Check out https://micronaut.io/launch/[Micronaut Launch] to get started!
When <>, you can call the CLI with the mn command.
[source,bash]
$ mn create-app my-app
A Micronaut framework CLI project can be identified by the micronaut-cli.yml file, which is included at the project root if it was generated via the CLI. This file will include the project's profile, default package, and other variables. The project's default package is evaluated based on the project name.
[source,bash]
$ mn create-app my-demo-app
results in the default package being my.demo.app.
You can supply your own default package when creating the application by prefixing the application name with the package:
[source,bash]
$ mn create-app example.my-demo-app
results in the default package being example.
== Interactive Mode
If you run mn without any arguments, the Micronaut CLI launches in interactive mode. This is a shell-like mode which lets you run multiple CLI commands without re-initializing the CLI runtime, and is especially suitable when you use code-generation commands (such as create-controller), create multiple projects, or are just exploring CLI features. Tab-completion is enabled, enabling you to hit the TAB key to see possible options for a given command or flag.
[source,bash]
$ mn | Starting interactive mode... | Enter a command name to run. Use TAB for completion: mn>
== Help and Info
General usage information can be viewed using the help flag on a command.
[source,bash]
mn> create-app -h
Usage: mn create-app [-hivVx] [--list-features] [-b=BUILD-TOOL] [--jdk=
[-t=TEST] [-f=FEATURE[,FEATURE...]]... [NAME]
Creates an application
[NAME] The name of the application to create.
-b, --build=BUILD-TOOL Which build tool to configure. Possible values: gradle, gradle_kotlin,
maven.
-f, --features=FEATURE[,FEATURE...]
-h, --help Show this help message and exit.
-i, --inplace Create a service using the current directory
--jdk, --java-version=
The JDK version the project should target
-l, --lang=LANG Which language to use. Possible values: java, groovy, kotlin.
--list-features Output the available features and their descriptions
-t, --test=TEST Which test framework to use. Possible values: junit, spock, kotest.
A list of available features can be viewed using the --list-features flag on any of the create commands.
[source,bash]
mn> create-app --list-features
Available Features
(+) denotes the feature is included by default
Name Description
Cache
cache-caffeine Adds support for cache using Caffeine (https://github.com/ben-manes/caffeine)
cache-ehcache Adds support for cache using EHCache (https://www.ehcache.org/)
cache-hazelcast Adds support for cache using Hazelcast (https://hazelcast.org/)
cache-infinispan Adds support for cache using Infinispan (https://infinispan.org/)
Src Main Docs Guide Cloud (`src/main/docs/guide/cloud.adoc`)
The majority of JVM frameworks in use today were designed before the rise of cloud deployments and microservice architectures. Applications built with these frameworks were intended to be deployed to traditional Java containers. As a result, cloud support in these frameworks typically comes as an add-on rather than as core design features.
Micronaut framework was designed from the ground up for building microservices for the cloud. As a result, many key features that typically require external libraries or services are available within your application itself. To override one of the industry's current favorite buzzwords, Micronaut applications are "natively cloud-native".
The following are some cloud-specific features that are integrated directly into the Micronaut runtime:
- Distributed Configuration
- Service Discovery
- Client-Side Load-Balancing
- Distributed Tracing
- Serverless Functions
Many features in the Micronaut framework are heavily inspired by features from Spring and Grails. This is by design and helps developers who are already familiar with systems such as Spring Cloud.
The following sections cover these features and how to use them.
Src Main Docs Guide CommandLineApps (`src/main/docs/guide/commandLineApps.adoc`)
In certain cases you may wish to create standalone command-line (CLI) applications that interact with your Microservice infrastructure.
Examples of applications like this include scheduled tasks, batch applications and general command line applications.
In this case having a robust way to parse command line options and positional parameters is important.
Src Main Docs Guide Config (`src/main/docs/guide/config.adoc`)
Micronaut features a flexible configuration mechanism that allows reading configuration from a variety of sources into a unified model that can be bound to Java types annotated with <<configurationProperties, @ConfigurationProperties>>.
Configuration can by default be provided in Java properties files or https://www.json.org/json-en.html[JSON] with the ability to add support for more formats (such as YAML or Groovy configuration) by adding additional third-party libraries to your classpath. The convention is to search for a file named application.properties or application.json with support for other formats requiring additional dependencies as described by the following table:
.Supported Configuration Formats
|===
|Format|File|Dependency Required
| https://yaml.org[YAML]
|application.yml
|org.yaml:snakeyaml
| https://micronaut-projects.github.io/micronaut-groovy/latest/guide/#config[Groovy Config]
|application.groovy
|io.micronaut.groovy:micronaut-runtime-groovy
|https://github.com/lightbend/config/blob/main/HOCON.md[HOCON]
|application.conf
|io.micronaut.kotlin:micronaut-kotlin-runtime
|https://toml.io/en/[TOML]
|application.toml
|io.micronaut.toml:micronaut-toml
|===
In addition, Micronaut framework allows overriding any property via system properties or environment variables.
Each source of configuration is modeled with the link:{api}/io/micronaut/context/env/PropertySource.html[PropertySource] interface and the mechanism is extensible, allowing the implementation of additional link:{api}/io/micronaut/context/env/PropertySourceLoader.html[PropertySourceLoader] implementations.
Micronaut also supports in-file configuration imports via micronaut.config.import, which allows one configuration source to load additional sources recursively.
See <> for protocol-specific syntax (file, classpath, env, configtree) and optional import behavior.
Src Main Docs Guide Configurations (`src/main/docs/guide/configurations.adoc`)
Micronaut framework features several built-in configurations that enable integration with common databases and other servers.
Src Main Docs Guide ContextPropagation (`src/main/docs/guide/contextPropagation.adoc`)
Micronautβs Propagated Context API provides a single, consistent way to pass request scoped data across threads, reactors, and coroutines without relying directly on ThreadLocal.
=== PropagatedContext building blocks
The central type is api:core.propagation.PropagatedContext[]. It is an immutable container that holds any number of api:core.propagation.PropagatedContextElement[] instances. Elements are lightweight descriptors of the data you want to make available downstreamβfor example a trace identifier, security information, or a copy of the logging MDC. Because the context is immutable you build a new instance whenever you add or remove an element:
[source,java]
PropagatedContext base = PropagatedContext.getOrEmpty(); PropagatedContext enriched = base.plus(new TraceIdContextElement(traceId));
If an element needs to interact with thread-local state (for example to update the MDC) it can also implement api:core.propagation.ThreadPropagatedContextElement[], which allows Micronaut to capture the previous thread-local state and restore it later. When you want to surface a JDK ScopedValue, implement api:core.propagation.ScopedValuePropagatedContextElement[] and Micronaut will automatically bind the provided ScopedValue and value while the context is propagated.
.Example MDC element implementing `ThreadPropagatedContextElement` [source,java]
include::{testsuitejava}/propagation/MdcPropagationContext.java[tags="class"]
<1> Accept the MDC state that should be propagated.
<2> In updateThreadContext push the MDC state into the current thread.
<3> Capture the previous MDC so it can be restored.
<4> Restore the previous MDC state when the scope exits.
NOTE: api:core.propagation.ThreadPropagatedContextElement[] mirrors the behaviour of Kotlinβs kotlinx.coroutines.ThreadContextElement, which makes it easy to integrate with coroutines.
NOTE: api:core.propagation.ScopedValuePropagatedContextElement[] lets you expose existing ScopedValue keys so Micronaut can apply ScopedValue.where(key, value) for every element before executing your code.
Example of chaining multiple scoped value bindings:
[source,java]
class UserContextElement implements ScopedValuePropagatedContextElement
static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
private final String userId;
UserContextElement(String userId) {
this.userId = userId;
}
@Override
public ScopedValue<String> scopedValue() {
return USER_ID;
}
@Override
public String scopedValueValue() {
return userId;
}
String currentUserId() {
return USER_ID.isBound() ? USER_ID.get() : null;
}}
To bind and read the value purely with the JDK ScopedValue API:
[source,java]
PropagatedContext context = PropagatedContext.getOrEmpty()
.plus(new UserContextElement("42"));
String userId = context.propagate(() -> {
if (UserContextElement.USER_ID.isBound()) {
return UserContextElement.USER_ID.get();
}
return null;
});
==== Hybrid example: thread local by default, scoped value when enabled
Sometimes you need a single element that works in both propagation modes. Implementing both interfaces lets you update a ThreadLocal in the default mode and automatically bind a ScopedValue when scoped-value mode is enabled:
[source,java]
class RequestContextElement implements ScopedValuePropagatedContextElement
ThreadPropagatedContextElement
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
private static final ThreadLocal<String> THREAD_REQUEST_ID = new ThreadLocal<>();
private final String requestId;
RequestContextElement(String requestId) {
this.requestId = requestId;
}
String currentRequestId() {
return REQUEST_ID.isBound() ? REQUEST_ID.get() : THREAD_REQUEST_ID.get();
}
@Override
public ScopedValue<String> scopedValue() {
return REQUEST_ID;
}
@Override
public String scopedValueValue() {
return requestId;
}
@Override
public @Nullable String updateThreadContext() {
if (REQUEST_ID.isBound()) {
return null; // scoped value already in place
}
String previous = THREAD_REQUEST_ID.get();
THREAD_REQUEST_ID.set(requestId);
return previous;
}
@Override
public void restoreThreadContext(@Nullable String previous) {
if (previous == null && REQUEST_ID.isBound()) {
return; // we skipped thread local updates
}
if (previous == null) {
THREAD_REQUEST_ID.remove();
} else {
THREAD_REQUEST_ID.set(previous);
}
}}
When Micronaut runs in thread-local mode the element transparently updates the ThreadLocal, so currentRequestId() keeps working with existing integrations. If you switch to scoped-value mode, the ScopedValue binding happens first, updateThreadContext() returns null, and the ThreadLocal branch is skipped entirely.
snippet::io.micronaut.docs.propagation.MdcService[tags="createUser", indent=0, title="MDC propagation example"]
IMPORTANT: Since Micronaut Framework 4 the runtime no longer βcaptures whatever happens to be in scope.β You are responsible for obtaining the current context with PropagatedContext.getOrEmpty(), adding elements, and propagating the resulting instance explicitly.
=== Using the propagate(β¦) helpers
Once you have a context you can execute work with that context in scope by using one of the helper methods. Each helper returns the result of the lambda you pass in, and inside that lambda PropagatedContext.get() (or getOrEmpty()) gives you the propagated elements:
[source,java]
PropagatedContext context = PropagatedContext.getOrEmpty()
.plus(new TraceIdContextElement(traceId));
String response = context.propagate(() -> {
// Inside the lambda the context is bound
TraceIdContextElement element = PropagatedContext.get().get(TraceIdContextElement.class);
return downstream.callWith(element.traceId());
}); // Supplier
context.propagate(() -> {
TraceIdContextElement element = PropagatedContext.get().get(TraceIdContextElement.class);
logger.debug("executed with propagated state {}", element.traceId());
}); // Runnable
Integer status = context.propagateCall(() -> client.status()); // Callable
For cases where you need to hand off workβfor example to an executorβyou can also pre-wrap a Runnable, Callable, or Supplier via PropagatedContext.wrap(β¦).
=== Executor beans
ExecutorService beans propagate the context automatically. This covers the built-in io, blocking, virtual and scheduled executors, executors configured under micronaut.executors.*, and other beans exposed as ExecutorService. A task submitted to one of them captures the context of the submitting thread and runs with it. The worker thread doesn't keep the context once the task completes. This works in both the thread-local and scoped-value modes. When no context is in scope, the task is submitted as-is.
So code that hands work to an executor bean, for example a messaging listener configured with a named executor or a method annotated with @ExecuteOn, sees the caller's trace and MDC state without wrapping the task itself.
To turn this off for one executor configured under micronaut.executors.*, set propagate-context to false (default: true):
[configuration]
micronaut:
executors:
my-pool:
type: fixed
number-of-threads: 4
propagate-context: false
The context is captured when a task is submitted or scheduled, so it stays referenced until the task has run. This matters for the ScheduledExecutorService methods:
- A periodic task (
scheduleAtFixedRateorscheduleWithFixedDelay) doesn't capture the context, because it would keep that context alive until it is cancelled. It runs without the context of the code that scheduled it. To run it with that context, wrap it yourself, for examplescheduler.scheduleAtFixedRate(PropagatedContext.wrapCurrent(this::poll), 0, 30, TimeUnit.SECONDS). - A one-shot task (
schedule) captures the context. A task that schedules itself again withschedule, for example a polling loop or a retry with backoff, therefore passes its context to the next run, and a request's context can outlive the request.
@Scheduled methods are scheduled at startup, without a context. When code that runs with a request's context schedules long-lived work that reschedules itself, schedule it from an empty context so that the request's MDC or tracing state isn't kept:
[source,java]
PropagatedContext.empty().propagate(() ->
scheduler.schedule(this::pollAndReschedule, 30, TimeUnit.SECONDS)
);
Alternatively, set propagate-context: false on an executor that runs such work.
NOTE: Only tasks passed to the executor's own methods (execute, submit, invokeAll, invokeAny and schedule) are wrapped. Work that a task forks directly onto a ForkJoinPool, for example with ForkJoinTask.fork(), doesn't go through the wrapper.
=== Default thread-local propagation with optional Scoped Values
Micronaut Framework 5 targets Java 25 by default. Since Java 21 the JDK ships Scoped Values (java.lang.ScopedValue) as a structured alternative to classic thread locals. Even though Scoped Values are available on Java 25, Micronaut keeps thread-local as the default propagation mode so existing integrations that require thread-local state continue to work without extra configuration.
Scoped Values still participate in virtual threads, enforce well-defined lifetimes, and prevent the accidental leaks that long-running thread pools often suffer when ThreadLocal is used directly. When you want those semantics, set the propagation mode to scoped-value explicitly.
=== Avoid try-with-resources unless thread-local propagation is mandatory
The api:core.propagation.PropagatedContext[] interface exposes a propagate() method that returns an auto-closeable scope so that you can use Javaβs try-with-resources syntax. The lambda helpers remain the preferred approach:
[source,java]
PropagatedContext context = PropagatedContext.getOrEmpty().plus(new CustomElement());
context.propagate(() -> {
// work that needs the propagated state
});
The try-with-resources pattern exists primarily for legacy integrations that require ThreadLocal propagation. Whenever you can, call one of the lambda-based propagate(β¦) helpers shown earlier insteadβthey work in both propagation modes. Only fall back to the scope API when the surrounding code expects a ThreadLocal to be present. Because the scope relies on thread-local state, invoking propagate() while the mode is scoped-value throws an IllegalStateException instructing you to switch back to thread-local support.
==== Integrations that must keep a context open across calls
A scoped value is only bound for the duration of a single callback invocation: it cannot be bound by one method and unbound by a later one. As a result there is no equivalent of an open/close scope in scoped-value mode. Some integrations nonetheless cannot use a callback because the context must stay in scope across separate calls into user code, for example an Iterator that brings the context of a record into scope when next() is called and takes it out of scope on the following next() or close().
Such integrations can call PropagatedContext.supportsScopes() to check whether the current mode supports scopes, or use propagateIfSupported(), which behaves like propagate() in thread-local mode and returns a no-op scope (without propagating anything) in scoped-value mode instead of throwing:
[source,java]
PropagatedContext.Scope scope = context.propagateIfSupported(); // no-op in scoped-value mode // ... later, for example on the next call to Iterator.next() scope.close();
To opt into scoped-value mode globally (default: thread-local), set micronaut.propagation:
[configuration]
micronaut: propagation: scoped-value
Or change it programmatically during application bootstrap:
[source,java]
import io.micronaut.context.annotation.Context;
import io.micronaut.context.event.ContextStartedEvent;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.core.propagation.PropagatedContextConfiguration;
@Context
class ScopedValuePropagationListener implements ApplicationEventListener
@Override
public void onApplicationEvent(ContextStartedEvent event) {
PropagatedContextConfiguration.set(PropagatedContextConfiguration.Mode.SCOPED_VALUE);
}}
Switching back to thread-local mode later is as simple as calling PropagatedContextConfiguration.set(PropagatedContextConfiguration.Mode.THREAD_LOCAL);.
NOTE: Prefer scoped-value when your integrations only need lambda-based propagation. Scoped Values integrate seamlessly with virtual threads, express the intent that the state only lives within a structured scope, and eliminate the risk of leaking ThreadLocal data between unrelated requests.
=== When scoped values are not sufficient
Some integrations still require ThreadLocal semantics because their APIs expose explicit βupdateβ callbacks instead of accepting a lambda to execute with the context in scope. In those scenarios switch Micronautβs propagation mode to thread-local before invoking the libraries involved. Common examples include:
- Reactor instrumentation β certain Micrometer/
ContextViewbridges expose callbacks that pull data from aThreadLocalinstead of accepting a lambda. If your reactive pipelines depend on that behaviour, configure thread-local propagation so the state is visible.
After the critical section completes you can switch the propagation mode back to scoped-value to regain the benefits of structured lifetimes.
NOTE: Kotlin coroutines work in both modes. A coroutine is resumed and suspended by separate callbacks, so in scoped-value mode Micronaut binds the coroutine's context to the thread it runs on instead of binding a ScopedValue: PropagatedContext.get() and the api:core.propagation.ThreadPropagatedContextElement[] elements see the coroutine's context, and a context propagated with a propagate(β¦) helper inside the coroutine takes precedence for the duration of the callback. The api:core.propagation.ScopedValuePropagatedContextElement[] elements of the coroutine's context are only bound inside such a callback.
Src Main Docs Guide DevMode (`src/main/docs/guide/devMode.adoc`)
The micronaut-dev module runs an application so that a change to its sources takes effect without restarting the JVM. A launcher, api:dev.MicronautDevMain[]:
- loads the application's classes and resources through a reloadable class loader;
- watches the source and resource directories;
- recompiles what changed with the JDK compiler;
- restarts the application context on a new generation of that loader.
The JVM, the compiler's warm state and the singletons a api:context.reload.BeanRetentionPolicy[] retains, such as a connection pool, survive the restart.
The module is experimental. The build plugins for Gradle and Maven start it for you. The sections below describe what happens underneath, so that you can configure it, retain your own beans, and write beans that follow a reload.
Src Main Docs Guide HttpClient (`src/main/docs/guide/httpClient.adoc`)
Client communication between Microservices is a critical component of any Microservice architecture. With that in mind, Micronaut framework includes an HTTP client that has both a low-level API and a higher-level AOP-driven API.
TIP: Regardless whether you choose to use Micronaut's HTTP server, you may wish to use the Micronaut HTTP client in your application since it is a feature-rich client implementation.
Src Main Docs Guide HttpServer (`src/main/docs/guide/httpServer.adoc`)
[TIP] .Using the CLI
If you create your project using the Micronaut CLI `create-app` command, the `http-server` dependency is included by default.
Micronaut framework includes both non-blocking HTTP server and client APIs based on https://netty.io[Netty].
The goal of the HTTP server is to make it as easy as possible to expose services to be consumed by HTTP clients and web pages, regardless of the language they are written in. There is support for rendering HTML from a variety of templating frameworks in the https://micronaut-projects.github.io/micronaut-views/latest/guide/[Micronaut Views] module. To use the HTTP server you need the http-server-netty dependency in your build:
dependency:micronaut-http-server-netty[]
A "Hello World" server application can be seen below:
snippet::io.micronaut.docs.server.intro.HelloController[tags="imports,class"]
<1> The class is defined as a controller with the link:{api}/io/micronaut/http/annotation/Controller.html[@Controller] annotation mapped to the path /hello
<2> The method responds to a GET requests to /hello and returns a response with a text/plain content type
<3> By defining a method named index, by convention the method is exposed via the /hello URI
Src Main Docs Guide Introduction (`src/main/docs/guide/introduction.adoc`)
The Micronaut Framework is a modern JVM-based framework designed for building modular, easily testable JVM applications with support for Java, Kotlin, Groovy and Python.
The Micronaut framework was originally created by a team who had also worked on the Grails framework. The Micronaut framework takes inspiration from lessons learned over the years building real-world applications from monoliths to microservices using Spring, Spring Boot and the Grails framework. The core team continues to develop and maintain the Micronaut project through the support of the Commonhaus Foundation.
The Micronaut framework aims to provide all the tools necessary to build JVM applications including:
- Dependency Injection and Inversion of Control (IoC)
- Aspect Oriented Programming (AOP)
- Sensible Defaults and Auto-Configuration
With the Micronaut framework you can build Message-Driven Applications, Command Line Applications, HTTP Servers and more whilst for Microservices in particular Micronaut also provides:
- Distributed Configuration
- Service Discovery
- HTTP Routing
- Client-Side Load Balancing
At the same time, the Micronaut framework aims to avoid the downsides of other frameworks by supporting:
- Fast startup time
- Reduced memory footprint
- Minimal use of reflection
- Minimal use of proxies
- No runtime bytecode generation
- Easy Unit Testing
- Compilation to Native code with GraalVM Native Image
Historically, JVM frameworks were not designed to run in scenarios such as serverless functions, Android apps, or low memory footprint microservices. In contrast, the Micronaut framework is designed to be suitable for all of these scenarios and more.
This goal is achieved through the use of compilation time processors, which are usable on any JVM language that supports them, as well as an HTTP Server (with several runtimes https://netty.io/[Netty], link:{micronautservletdocs}#jetty[Jetty], link:{micronautservletdocs}#tomcat[Tomcat], link:{micronautservletdocs}#undertow[Undertow]...) and an HTTP Client (with several runtimes <<nettyHttpClient, Netty>>, <<jdkHttpClient, Java HTTP Client>>, ...). To provide a similar programming model to Spring and Grails, these annotation processors precompile the necessary metadata to perform DI, define AOP proxies and configure your application to run in a low-memory environment.
APIs in Micronaut are designed to be easy to learn when coming from existing frameworks in the Java ecosystem.
Src Main Docs Guide Ioc (`src/main/docs/guide/ioc.adoc`)
https://en.wikipedia.org/wiki/Dependency_injection[Dependency Injection] (often referred to as https://en.wikipedia.org/wiki/Inversion_of_control[Inversion of Control]) is a pattern that allows your code to remain independent of configuration and allows the definition of loosely coupled applications. Reducing coupling increases the ability to test your application by ensuring code is not tied directly to an external systems (like a database).
Unlike other frameworks which rely on runtime reflection and proxies, the Micronaut framework uses compile time data to implement dependency injection.
This is a similar approach taken by tools such as Google https://google.github.io/dagger/[Dagger], which is designed primarily with Android in mind. Micronaut, on the other hand, is designed for building server-side microservices and provides many of the same tools and utilities as other frameworks but without using runtime reflection or caching excessive amounts of runtime reflection metadata.
The goals of the Micronaut IoC container are summarized as:
- Use runtime reflection as a last resort
- Avoid runtime-generated proxies
- Optimize start-up time
- Reduce memory footprint
- Provide clear, understandable error handling
[.lang-kotlin.lang-java.lang-groovy]
Note that the IoC part of the Micronaut framework can be used completely independently of Micronaut for whatever application type you wish to build.
To do so, configure your build to include the micronaut-inject-java dependency as an annotation processor.
If you are creating custom compile-time annotations or annotation mappers, see <<annotationMetadata, Annotation Metadata>> for additional requirements around AnnotationMapper, NamedAnnotationMapper, and TypedAnnotationMapper.
The easiest way to do this is with Micronaut's Gradle or Maven plugins. For example with Gradle:
.Configuring Gradle [source,groovy,subs="attributes+"]
plugins {
id 'io.micronaut.library' version '4.4.4' // get latest version from https://plugins.gradle.org/plugin/io.micronaut.library
}
version "0.1"
group "com.example"
repositories {
mavenCentral()
}
micronaut {
version = "{version}" // <2>
}
<1> Define the https://plugins.gradle.org/plugin/io.micronaut.library[Micronaut Library plugin]
<2> Specify the Micronaut framework version to use
The entry point for IoC is then the api:context.ApplicationContext[] interface, which includes a run method. The following example demonstrates using it:
.Running the `ApplicationContext` [source,java]
try (ApplicationContext context = ApplicationContext.run()) { // <1>
MyBean myBean = context.getBean(MyBean.class); // <2>
// do something with your bean
}
<1> Run the api:context.ApplicationContext[]
<2> Retrieve a bean from the ApplicationContext
NOTE: The example uses Java https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html[try-with-resources] syntax to ensure the api:context.ApplicationContext[] is cleanly shutdown when the application exits.
Src Main Docs Guide LanguageSupport (`src/main/docs/guide/languageSupport.adoc`)
Micronaut framework supports any JVM language that implements the link:{jdkapi}/java.compiler/javax/annotation/processing/package-summary.html[Java Annotation Processor] API.
Although Groovy does not support this API, special support has been built using AST transformations. The current list of supported languages is: Java, Groovy, Kotlin (via the kapt tool), and Python.
NOTE: Theoretically any language that supports a way to analyze the AST at compile time could be supported. The pkg:inject.writer[] package includes language-neutral classes that build api:inject.BeanDefinition[] classes at compile time using the ASM tool.
The following sections cover language-specific features and considerations for using Micronaut.
Src Main Docs Guide Logging (`src/main/docs/guide/logging.adoc`)
The Micronaut framework uses https://www.slf4j.org/[Slf4j] to log messages. The default implementation for applications created via Micronaut Launch is https://logback.qos.ch/[Logback]. Any other Slf4j implementation is supported, however.
Src Main Docs Guide Management (`src/main/docs/guide/management.adoc`)
[TIP] .Using the CLI
If you create your project using the Micronaut CLI, supply the `management` feature to configure the management endpoints in your project:
$ mn create-app my-app --features management
====
Inspired by Spring Boot and Grails, the Micronaut management dependency adds support for monitoring of your application via endpoints: special URIs that return details about the health and state of your application. The management endpoints are also integrated with Micronaut's security dependency, allowing for sensitive data to be restricted to authenticated users in your security system (see link:https://micronaut-projects.github.io/micronaut-security/latest/guide/#builtInEndpointsAccess[Built-in Endpoints Access] in the Security section).
To use the management features described in this section, add this dependency to your build:
dependency:micronaut-management[]
Src Main Docs Guide Messaging (`src/main/docs/guide/messaging.adoc`)
In the past, with monolithic applications, message listeners that listened to messages from messaging systems would frequently be embedded in the same application unit.
In Microservice architectures it is common to have individual Microservice applications that are driven by a message system such as RabbitMQ or Kafka.
In fact a Message-driven Microservice may not even feature an HTTP endpoint or HTTP server (although this can be valuable from a health check and visibility perspective).
Src Main Docs Guide Multitenancy (`src/main/docs/guide/multitenancy.adoc`)
See the link:https://micronaut-projects.github.io/micronaut-multitenancy/latest/guide[Micronaut Multitenancy documentation] to learn about Micronaut's support for common tasks such as tenant resolution for multi-tenancy-aware Micronaut applications.
Src Main Docs Guide QuickStart (`src/main/docs/guide/quickStart.adoc`)
The following sections walk you through a Quick Start on how to use the Micronaut framework to set up a basic "Hello World" application.
Before getting started ensure you have a Java 8 or higher JDK installed.
[.lang-java.lang-groovy.lang-kotlin]
It is recommended that you use a <> such as IntelliJ IDEA.
[.lang-python]
It is recommended that you use an IDE with Python support, such as IntelliJ IDEA or Visual Studio Code.
Src Main Docs Guide Resources (`src/main/docs/guide/resources.adoc`)
Micronaut Framework provides support for loading resources from files into memory, rooted at the api:core.io.ResourceLoader[] API. Built-in implementations include
- api:core.io.file.DefaultFileSystemResourceLoader[]
- api:core.io.scan.DefaultClassPathResourceLoader[]
- api:core.io.value.StringResourceLoader[]
- api:core.io.value.Base64ResourceLoader[]
A convenience class api:core.io.ResourceResolver[] is provided that leverages these implementations. The following example illustrates using the API to read a text file from the classpath.
snippet::io.micronaut.docs.resources.MyResourceLoader[tags="class",indent=0,title="ResourceResolver Example"]
<1> Injects an instance of ResourceResolver
<2> Uses the ResourceResolver API to get a URL to a file in the classpath, if it exists.
<3> api:core.io.IOUtils[] provides further support for I/O operations, in this case reading the file contents into a String.
Areas of the framework that load resources using ResourceResolver include:
- Static HTTP resources
- SSL key and trust stores
- Dynamic configuration
To use a fixed value for a resource instead of loading it from the file system or classpath, use the string: andbase64: prefixes. While string: returns the value as is (decoded as UTF-8), base64: first decodes the value from
Base64. This allows you to serve e.g. a small binary as a static HTTP file.
Src Main Docs Guide Security (`src/main/docs/guide/security.adoc`)
The Micronaut framework has a full-featured security solution for all common security patterns.
See the documentation for link:https://micronaut-projects.github.io/micronaut-security/latest/guide/[Micronaut Security] for more information on how to secure your applications.
Src Main Docs Guide ServerlessFunctions (`src/main/docs/guide/serverlessFunctions.adoc`)
Serverless architectures, where you deploy functions that are fully managed by a Cloud environment and are executed in ephemeral processes, require a unique approach.
Traditional frameworks like Grails and Spring are not really suitable since low memory consumption and fast startup time are critical, since the Function as a Service (FaaS) server typically spins up your function for a period using a cold start and then keeps it warm.
Micronaut's compile-time approach, fast startup time, and low memory footprint make it a great candidate for developing functions, and the Micronaut framework includes dedicated support for developing and deploying functions to AWS Lambda, Google Cloud Function, Azure Function, and any FaaS system that supports running functions as containers (such as OpenFaaS, Rift or Fn).
There are generally two approaches to writing functions with Micronaut:
. Low-level functions written using the native API of the function platform
. Higher-level functions where you simply define controllers as you normally do in a typical Micronaut application and deploy to the function platform.
The first has marginally less startup time overhead and is typically used for non-HTTP functions such as functions that listen to an event or background functions.
The second is only for HTTP functions and is useful for users who want to take a slice of an existing application and deploy it as a serverless function. If cold start performance is a concern it is recommended that you consider building a native image with GraalVM for this option.
Src Main Docs Guide Toc Python.Yml (`src/main/docs/guide/toc-python.yml`)
introduction:
title: Introduction
upgrading: Upgrading your Micronaut Application
quickStart:
title: Quick Start
buildCLI: Install the CLI
creatingServer: Creating a Server Application
ideSetup:
title: Setting up an IDE
ideaSetup: IntelliJ IDEA
vsCodeSetup: Visual Studio Code
eclipseSetup: Eclipse
creatingClient: Creating a Client
deployingApp: Deploying the Application
ioc:
title: Dependency Injection
beans: Defining Beans
how: How Does it Work?
iocDebugging: Debugging Dependency Injection
beanContext: The BeanContext
injection:
title: Dependency Injection Types
constructorInjection: Constructor Injection
fieldInjection: Field Injection
methodInjection: Method Injection
nullableInjection: Nullable Injection
types: Injectable Container Types
qualifiers: Bean Qualifiers
typed: Limiting Injectable Types
scopes:
title: Scopes
builtInScopes:
title: Built-In Scopes
eagerInit: Eager Initialization of Singletons
refreshable: Refreshable Scope
metaScopes: Scopes on Meta Annotations
factories: Bean Factories
conditionalBeans: Conditional Beans
replaces: Bean Replacement
beanConfigurations: Bean Configurations
lifecycle: Life-Cycle Methods
gracefulShutdown: Graceful Shutdown
contextEvents: Context Events
events: Bean Events
introspection:
title: Bean Introspection
atIntrospected: Use the @Introspected Annotation
staticAtCreator: Static Creator Methods
introspectedAnnotationOnAConfigurationClass: Use the @Introspected Annotation on a Configuration Class
beanWrapperApi: The BeanWrapper API
beanMappers:
title: Bean Mappers
beanMappersMerging:
title: Bean Mappers Merging
beanMappersMergingStrategy: Merging Strategy
beanValidation: Bean Validation
config:
title: Application Configuration
environments:
title: The Environment
environmentPriority: Environment Priority
disablingEnvironmentDetection: Disabling Environment Detection
defaultEnvironment: Default Environment
banner: Micronaut Banner
propertySource: Externalized Configuration with PropertySources
valueAnnotation: Configuration Injection
evaluatedExpressions: Expression Language
configurationProperties: Configuration Properties
customTypeConverter: Custom Type Converters
eachProperty: Using @EachProperty to Drive Configuration
eachBean: Using @EachBean to Drive Configuration
immutableConfig: Immutable Configuration
validation: Validating Configuration
bootstrap: Bootstrap Configuration
jmx:
title: JMX Support
aop:
title: Aspect Oriented Programming
aroundAdvice: Around Advice
introductionAdvice: Introduction Advice
adapterAdvice: Method Adapter Advice
lifecycleAdvice: Bean Life Cycle Advice
interceptorLifecycle: Interceptor Life Cycle
validation:
title: Validation Advice
caching:
title: Cache Advice
retry:
title: Retry Advice
scheduling:
title: Scheduled Tasks
httpServer:
title: The HTTP Server
runningServer: Running the Embedded Server
runningSpecificPort: Running Server on a Specific Port
routing: HTTP Routing
binding: Simple Request Binding
customArgumentBinding: Custom Argument Binding
hostResolution: Host Resolution
localeResolution: Locale Resolution
clientIpAddress: Client IP Address
requestResponse: The HttpRequest and HttpResponse
statusAnnotation: Response Status
contentDispositionAnnotation: Response Content-Disposition
producesAnnotation: Response Content-Type
consumesAnnotation: Accepted Request Content-Type
reactiveServer:
title: Reactive HTTP Request Processing
bodyAnnotation: Using the @Body Annotation
reactiveResponses: Reactive Responses
jsonBinding:
title: JSON Binding
serializeUsingMicronautSerialization: Serialize using Micronaut Serialization
serializationUsingJacksonDatabind: Serialization using Jackson Databind
jsonMapper: JsonMapper
bindingUsingReactiveFrameworks: Binding using Reactive Frameworks
bindingUsingCompletableFuture: Binding Using CompletableFuture
bindingUsingPOJOs: Binding using POJOs
plainTextResponses: Plain Text Responses
datavalidation:
title: Data Validation
validationGroups: Validation Groups
staticResources: Serving Static Resources
errorHandling:
title: Error Handling
statusHandlers: Status Handlers
localErrorHandling: Local Error Handling
globalErrorHandling: Global Error Handling
exceptionHandler:
title: ExceptionHandler
builtInExceptionHandlers: Built-In Exception Handlers
customExceptionHandler: Custom Exception Handler
errorFormatting: Error Formatting
apiVersioning: API Versioning
formData: Handling Form Data
serverIO: Writing Response Data
uploads: File Uploads
transfers: File Transfers
filters:
title: HTTP Filters
filterPatterns: Filter Patterns
filtermethods:
title: Filter Methods
filtermethodsexample: Server Filter with Filter Methods
filtermethodproceed: Proceeding with Filter Methods
errorStates: Error States
continuations: Continuations
order: Filter Order
httpServerFilter:
title: HttpServerFilter
httpServerFilterExample: HttpServerFilter Example
httpServerFilterErrorStates: HttpServerFilter Error States
byteBody:
title: Advanced body access
byteBodyPrimary: Primary operations
byteBodySplitting: Splitting
byteBodyExample: Example
sessions: HTTP Sessions
sse: Server Sent Events
websocket:
title: WebSocket Support
websocketServer: Using @ServerWebSocket
websocketClient: Using @ClientWebSocket
http2Server: HTTP/2 Support
http3Server: HTTP/3 Support
serverEvents: Server Events
serverConfiguration:
title: Configuring the HTTP Server
threadPools:
title: Configuring Server Thread Pools
blockingOperations: Blocking Operations
virtualThreads: Virtual Threads
atBlocking: '@Blocking'
nettyClientPipeline: Configuring the Netty Client Pipeline
nettyServerPipeline: Configuring the Netty Server Pipeline
listener: Advanced Listener Configuration
cors:
title: Configuring CORS
annotationBasedCors: Annotation-based CORS Configuration
corsConfiguration: CORS via Configuration
corsAllowedOrigins: Allowed Origins
corsAllowedMethods: Allowed Methods
corsAllowedHeaders: Allowed Headers
corsExposedHeaders: Exposed Headers
corsAllowCredentials: Allow Credentials
corsAllowPrivateNetwork: Allow Private Network
corsMaxAge: Max Age
corsMultipleHeaderValues: Multiple Header Values
https: Securing the Server with HTTPS
dualProtocol: Enabling HTTP and HTTPS
accessLogger: Enabling Access Logger
secondaryServers: Starting Secondary Servers
views:
title: Server Side View Rendering
openapi: OpenAPI / Swagger Support
graphql: GraphQL Support
httpClient:
title: The HTTP Client
httpClientImplementations:
title: HTTP Client Implementations
nettyHttpClient: HTTP Client based on Netty
jdkHttpClient:
title: HTTP Client based on the Java HTTP Client
lowLevelOrHighLevel: Low-Level and High-Level APIs
lowLevelHttpClient:
title: Using the Low-Level HTTP Client
clientBasics: Sending your first HTTP request
clientPostRequests: Posting a Request Body
clientUploads: Multipart Client Uploads
clientStreaming: Streaming JSON over HTTP
clientConfiguration: Configuring HTTP clients
lowLevelClientError: Error Responses
bindErrors: Bind Errors
proxyClient: Proxying Requests with ProxyHttpClient
clientAnnotation:
title: Declarative HTTP Clients with @Client
clientParameters: Customizing Parameter Binding
clientAnnotationStreaming: Streaming with @Client
clientError: Error Responses
clientHeaders: Customizing Request Headers
clientJackson: Customizing Jackson Settings
clientRetry: Retry and Circuit Breaker
clientFallback: Client Fallbacks
clientFilter: HTTP Client Filters
clientHttp2: HTTP/2 Support
clientHttp3: HTTP/3 Support
clientSample: HTTP Client Sample
nettyPooling: Connection pooling in the netty client
certificates: Certificate providers
contextPropagation:
title: Context propagation
reactorContextPropagation: Reactor context propagation
httpFilterContextPropagation: HTTP filters context propagation
cloud:
title: Cloud Native Features
cloudConfiguration:
title: Cloud Configuration
distributedConfiguration: Distributed Configuration
distributedConfigurationConsul: HashiCorp Consul Support
distributedConfigurationVault: HashiCorp Vault Support
distributedConfigurationSpringCloud: Spring Cloud Config Support
distributedConfigurationAwsParameterStore: AWS Parameter Store Support
distributedConfigurationOracleCloudVault: Oracle Cloud Vault Support
distributedConfigurationGoogleCloudSecretManager: Google Cloud Secret Manager Support
distributedConfigurationKubernetes: Kubernetes Support
serviceDiscovery:
title: Service Discovery
serviceDiscoveryConsul: Consul Support
serviceDiscoveryEureka: Eureka Support
serviceDiscoveryKubernetes: Kubernetes Support
serviceDiscoveryRoute53: AWS Route 53 Support
serviceDiscoveryManual: Manual Service Discovery Configuration
clientSideLoadBalancing:
title: Client Side Load Balancing
distributedTracing: Distributed Tracing
serverlessFunctions:
title: Serverless Functions
awsLambda: AWS Lambda
gcpFunction: Google Cloud Function
gcpCloudRun: Google Cloud Run
azureFunction: Azure Function
messaging:
title: Message-Driven Microservices
kafka: Kafka Support
rabbitmq: RabbitMQ Support
nats: Nats.io Support
commandLineApps:
title: Standalone Command Line Applications
picocli:
title: Picocli Support
configurations:
title: Configurations
dataAccess:
title: Configurations for Data Access
sqlSupport: Configuring a SQL Data Source
mongoSupport: Configuring MongoDB
neo4jSupport: Configuring Neo4j
postgresSupport: Configuring Postgres
redisSupport: Configuring Redis
cassandraSupport: Configuring Cassandra
liquibaseSupport: Configuring Liquibase
flywaySupport: Configuring Flyway
logging:
title: Logging
loggingMessages: Logging Messages
loggingConfiguration: Configuration
logback: Logback
loggingSystem: Logging System
languageSupport:
title: Language Support
python:
title: Micronaut for Python
pythonThreading: Python Threading
typeOrdering: Type Ordering and Forward References
standardTypes: Standard Library Type Conversions
keywords: Python Keywords in Java APIs
overloads: Java Overloads and Variadic Parameters
pythonExceptions: Python Exceptions Extending Java Exceptions
extendingJavaClasses: Extending Java Classes
moduleScripts:
title: Module Scripts
testing: JUnit 5 Module Tests
graalTools: GraalPy Tools
asyncAwait:
title: Async/Await
asyncioRuntime: Netty-Backed Asyncio Runtime
asyncioNetworking: Asyncio Networking and TLS
asyncioUnsupported: Unsupported Asyncio APIs
graal:
title: Micronaut for GraalVM
graalServices: Microservices as GraalVM native images
management:
title: Management & Monitoring
buildingEndpoints:
title: Creating Endpoints
endpointAnnotation: The Endpoint Annotation
endpointMethod: Endpoint Methods
endpointSensitivity: Endpoint Sensitivity
endpointConfiguration: Endpoint Configuration
providedEndpoints:
title: Built-In Endpoints
beansEndpoint: The Beans Endpoint
infoEndpoint: The Info Endpoint
healthEndpoint:
title: The Health Endpoint
healthEndpointConfiguration: Health Endpoint Configuration
healthEndpointStatusCode: Health Status Codes
healthLogging: Health Logging
healthEndpointCustomization: Custom Health Indicator
healthMonitoringTask: Health Monitor Task
healthProvidedIndicators:
title: Provided Indicators
diskSpaceHealthIndicator: Disk Space Health Indicator
jdbcHealthIndicator: JDBC Health Indicator
discoveryClientHealthIndicator: Discovery Client Health Indicator
deadlockedThreads: Deadlocked Threads
metricsEndpoint: The Metrics Endpoint
refreshEndpoint: The Refresh Endpoint
routesEndpoint: The Routes Endpoint
loggersEndpoint: The Loggers Endpoint
cachesEndpoint: The Caches Endpoint
stopEndpoint: The Server Stop Endpoint
environmentEndpoint: The Environment Endpoint
threadDumpEndpoint: The ThreadDump Endpoint
security:
title: Security
multitenancy:
title: Multi-Tenancy
cli:
title: Micronaut CLI
createProject:
title: Creating a Project
comparingVersions: Comparing Versions
features: Features
commands: Commands
proxy: Proxy Configuration
i18n:
title: Internationalization
bundle: Resource Bundles
localizedMessageSource: Localized Message Source
resources:
title: Resources
appendix:
title: Appendices
architecture:
title: Micronaut Architecture
compilerArch: Compiler
annotationArch: Annotation Metadata
introspectionArch: Bean Introspections
iocArch: Bean Definitions
aopArch: AOP Proxies
containerArch: Application Context
httpServerArch: HTTP Server
faq: Frequently Asked Questions (FAQ)
usingsnapshots: Using Snapshots
problems: Common Problems
breaks: Breaking Changes
Src Main Docs Guide Toc.Yml (`src/main/docs/guide/toc.yml`)
introduction:
title: Introduction
whatsNew: What's New in Micronaut Framework 5.0.x
upgrading: Upgrading your Micronaut Application
quickStart:
title: Quick Start
buildCLI: Install the CLI
creatingServer: Creating a Server Application
ideSetup:
title: Setting up an IDE
ideaSetup: IntelliJ IDEA
vsCodeSetup: Visual Studio Code
eclipseSetup: Eclipse
netbeansSetup: Apache NetBeans
creatingClient: Creating a Client
deployingApp: Deploying the Application
ioc:
title: Dependency Injection
beans: Defining Beans
how: How Does it Work?
iocDebugging: Debugging Dependency Injection
beanContext: The BeanContext
injection:
title: Dependency Injection Types
constructorInjection: Constructor Injection
fieldInjection: Field Injection
methodInjection: Method Injection
nullableInjection: Nullable Injection
types: Injectable Container Types
qualifiers: Bean Qualifiers
typed: Limiting Injectable Types
scopes:
title: Scopes
builtInScopes:
title: Built-In Scopes
eagerInit: Eager Initialization of Singletons
refreshable: Refreshable Scope
metaScopes: Scopes on Meta Annotations
factories: Bean Factories
conditionalBeans: Conditional Beans
replaces: Bean Replacement
beanConfigurations: Bean Configurations
lifecycle: Life-Cycle Methods
gracefulShutdown: Graceful Shutdown
contextEvents: Context Events
events: Bean Events
introspection:
title: Bean Introspection
makingABeanAvailableForIntrospection: Making a Bean Available for Introspection
atIntrospected: Use the @Introspected Annotation
introspectedAccessorsStyle: Use @Introspected with @AccessorsStyle
accessKind: Bean Fields
introspectedProperty: Explicit Bean Properties
propertyMembers: Bean Property Members
introspectionIncludeInjected: Injection Points as Bean Properties
introspectionHierarchy: Type Hierarchy
typeHierarchyRecords: Type Hierarchy Records
atCreator: Constructor Methods
staticAtCreator: Static Creator Methods
introspectionConstructors: Describing Constructors
introspectionBuilders: Builders
introspectionEnums: Introspect Enums
introspectionTypeArguments: Type Arguments
introspectedAnnotationOnAConfigurationClass: Use the @Introspected Annotation on a Configuration Class
introspectExistingAnnotations: Write an AnnotationMapper to Introspect Existing Annotations
beanWrapperApi: The BeanWrapper API
kotlinAndBeanIntrospection: Kotlin and Bean Introspection
beanMappers:
title: Bean Mappers
beanMappersMerging:
title: Bean Mappers Merging
beanMappersMergingStrategy: Merging Strategy
beanValidation: Bean Validation
annotationMetadata: Bean Annotation Metadata
beanImport: Importing Beans from Libraries
reflection: Reflection Based Metadata
classImport: Importing Classes from Libraries
mixin: Modifying Class Annotations With A Mixin
nullabilityAnnotations:
title: Nullability Annotations
jspecify: JSpecify Nullability Annotations
springBeans: Micronaut Beans And Spring
android: Android Support
config:
title: Application Configuration
environments:
title: The Environment
environmentPriority: Environment Priority
disablingEnvironmentDetection: Disabling Environment Detection
defaultEnvironment: Default Environment
banner: Micronaut Banner
propertySource: Externalized Configuration with PropertySources
valueAnnotation: Configuration Injection
evaluatedExpressions: Expression Language
configurationProperties: Configuration Properties
customTypeConverter: Custom Type Converters
eachProperty: Using @EachProperty to Drive Configuration
eachBean: Using @EachBean to Drive Configuration
immutableConfig: Immutable Configuration
validation: Validating Configuration
bootstrap: Bootstrap Configuration
jmx:
title: JMX Support
aop:
title: Aspect Oriented Programming
aroundAdvice: Around Advice
introductionAdvice: Introduction Advice
adapterAdvice: Method Adapter Advice
lifecycleAdvice: Bean Life Cycle Advice
interceptorLifecycle: Interceptor Life Cycle
validation:
title: Validation Advice
caching:
title: Cache Advice
retry:
title: Retry Advice
scheduling:
title: Scheduled Tasks
springAop:
title: Bridging Spring AOP
httpServer:
title: The HTTP Server
runningServer: Running the Embedded Server
runningSpecificPort: Running Server on a Specific Port
routing: HTTP Routing
pathVariables: Path Variables
binding: Simple Request Binding
customArgumentBinding: Custom Argument Binding
hostResolution: Host Resolution
localeResolution: Locale Resolution
clientIpAddress: Client IP Address
requestResponse: The HttpRequest and HttpResponse
statusAnnotation: Response Status
contentDispositionAnnotation: Response Content-Disposition
producesAnnotation: Response Content-Type
consumesAnnotation: Accepted Request Content-Type
reactiveServer:
title: Reactive HTTP Request Processing
bodyAnnotation: Using the @Body Annotation
reactiveResponses: Reactive Responses
asyncRequestBody: Reading the Body Asynchronously
jsonBinding:
title: JSON Binding
serializeUsingMicronautSerialization: Serialize using Micronaut Serialization
serializationUsingJacksonDatabind: Serialization using Jackson Databind
jsonMapper: JsonMapper
bindingUsingReactiveFrameworks: Binding using Reactive Frameworks
bindingUsingCompletableFuture: Binding Using CompletableFuture
bindingUsingPOJOs: Binding using POJOs
jacksonConfiguration:
title: Jackson Configuration
jacksonConfigurationFeatures: Features
jacksonConfigurationFurtherCustomisingJsonFactory: Further customising JsonFactory
jacksonConfigurationSupportForJsonView: Support for @JsonView
jacksonConfigurationBeans: Beans
jacksonConfigurationServiceLoader: Service Loader
jacksonConfigurationNumberPrecision: Number Precision
plainTextResponses: Plain Text Responses
datavalidation:
title: Data Validation
validationGroups: Validation Groups
staticResources: Serving Static Resources
errorHandling:
title: Error Handling
statusHandlers: Status Handlers
localErrorHandling: Local Error Handling
globalErrorHandling: Global Error Handling
exceptionHandler:
title: ExceptionHandler
builtInExceptionHandlers: Built-In Exception Handlers
customExceptionHandler: Custom Exception Handler
errorFormatting: Error Formatting
apiVersioning: API Versioning
formData: Handling Form Data
form:
title: Forms
formBinding: Form Data Binding
formCapable: Detailed Form API
serverIO: Writing Response Data
uploads: File Uploads
formUploads: Form Data and File Uploads
transfers: File Transfers
filters:
title: HTTP Filters
filterPatterns: Filter Patterns
filtermethods:
title: Filter Methods
filtermethodsexample: Server Filter with Filter Methods
filtermethodproceed: Proceeding with Filter Methods
errorStates: Error States
continuations: Continuations
order: Filter Order
httpServerFilter:
title: HttpServerFilter
httpServerFilterExample: HttpServerFilter Example
httpServerFilterErrorStates: HttpServerFilter Error States
responseHeaderPopulator: Response Header Populators
byteBody:
title: Advanced body access
byteBodyPrimary: Primary operations
byteBodySplitting: Splitting
byteBodyTrailers: Trailers
byteBodyLimitReplay: Limiting and replaying
byteBodyExample: Example
functionalRoutes:
title: Functional Routes (Experimental)
functionalRoutesDeclaring: Declaring Routes
functionalRoutesRespond: Responses Without a Handler
functionalRoutesBody: Reading the Request Body
functionalRoutesForms: Forms and File Uploads
functionalRoutesStreaming: Server-Sent Events and Streamed Responses
functionalRoutesGroups: Route Groups and Path Prefixes
functionalRoutesFilters: Route, Group and Server Filters
functionalRoutesConditions: Conditions, Order, Ports and Attributes
functionalRoutesAnnotations: Annotations on Handler Routes
functionalRoutesErrors: Error and Status Routes
functionalRoutesLocators: Locator Routes
functionalRoutesWebSocket: WebSocket Routes
functionalRoutesLimitations: Limitations
sessions: HTTP Sessions
sse: Server Sent Events
websocket:
title: WebSocket Support
websocketServer: Using @ServerWebSocket
websocketClient: Using @ClientWebSocket
http2Server: HTTP/2 Support
http3Server: HTTP/3 Support
serverEvents: Server Events
trainingRun: Training Runs
serverConfiguration:
title: Configuring the HTTP Server
threadPools:
title: Configuring Server Thread Pools
blockingOperations: Blocking Operations
virtualThreads: Virtual Threads
atBlocking: '@Blocking'
nettyClientPipeline: Configuring the Netty Client Pipeline
nettyServerPipeline: Configuring the Netty Server Pipeline
listener: Advanced Listener Configuration
corsCrossOriginEmbedderPolicy: Cross-Origin-Embedder-Policy
corsCrossOriginResourcePolicy: Cross-Origin-Resource-Policy
cors:
title: Configuring CORS
annotationBasedCors: Annotation-based CORS Configuration
corsConfiguration: CORS via Configuration
corsAllowedOrigins: Allowed Origins
corsAllowedMethods: Allowed Methods
corsAllowedHeaders: Allowed Headers
corsExposedHeaders: Exposed Headers
corsAllowCredentials: Allow Credentials
corsAllowPrivateNetwork: Allow Private Network
corsMaxAge: Max Age
corsMultipleHeaderValues: Multiple Header Values
https: Securing the Server with HTTPS
dualProtocol: Enabling HTTP and HTTPS
accessLogger: Enabling Access Logger
secondaryServers: Starting Secondary Servers
views:
title: Server Side View Rendering
openapi: OpenAPI / Swagger Support
graphql: GraphQL Support
httpClient:
title: The HTTP Client
httpClientImplementations:
title: HTTP Client Implementations
nettyHttpClient: HTTP Client based on Netty
jdkHttpClient:
title: HTTP Client based on the Java HTTP Client
lowLevelOrHighLevel: Low-Level and High-Level APIs
lowLevelHttpClient:
title: Using the Low-Level HTTP Client
clientBasics: Sending your first HTTP request
clientPostRequests: Posting a Request Body
clientUploads: Multipart Client Uploads
clientStreaming: Streaming JSON over HTTP
clientConfiguration: Configuring HTTP clients
lowLevelClientError: Error Responses
bindErrors: Bind Errors
proxyClient: Proxying Requests with ProxyHttpClient
rawClient: Raw Exchanges with RawHttpClient
clientAnnotation:
title: Declarative HTTP Clients with @Client
clientParameters: Customizing Parameter Binding
clientAnnotationStreaming: Streaming with @Client
clientError: Error Responses
clientHeaders: Customizing Request Headers
clientJackson: Customizing Jackson Settings
clientRetry: Retry and Circuit Breaker
clientFallback: Client Fallbacks
clientFilter: HTTP Client Filters
clientHttp2: HTTP/2 Support
clientHttp3: HTTP/3 Support
clientSample: HTTP Client Sample
nettyPooling: Connection pooling in the netty client
certificates: Certificate providers
contextPropagation:
title: Context propagation
reactorContextPropagation: Reactor context propagation
httpFilterContextPropagation: HTTP filters context propagation
cloud:
title: Cloud Native Features
cloudConfiguration:
title: Cloud Configuration
distributedConfiguration: Distributed Configuration
distributedConfigurationConsul: HashiCorp Consul Support
distributedConfigurationVault: HashiCorp Vault Support
distributedConfigurationSpringCloud: Spring Cloud Config Support
distributedConfigurationAwsParameterStore: AWS Parameter Store Support
distributedConfigurationOracleCloudVault: Oracle Cloud Vault Support
distributedConfigurationGoogleCloudSecretManager: Google Cloud Secret Manager Support
distributedConfigurationKubernetes: Kubernetes Support
serviceDiscovery:
title: Service Discovery
serviceDiscoveryConsul: Consul Support
serviceDiscoveryEureka: Eureka Support
serviceDiscoveryKubernetes: Kubernetes Support
serviceDiscoveryRoute53: AWS Route 53 Support
serviceDiscoveryManual: Manual Service Discovery Configuration
clientSideLoadBalancing:
title: Client Side Load Balancing
distributedTracing: Distributed Tracing
serverlessFunctions:
title: Serverless Functions
awsLambda: AWS Lambda
gcpFunction: Google Cloud Function
gcpCloudRun: Google Cloud Run
azureFunction: Azure Function
messaging:
title: Message-Driven Microservices
kafka: Kafka Support
rabbitmq: RabbitMQ Support
nats: Nats.io Support
devMode:
title: Development Mode
devModeLaunch: Launching in Development Mode
devModeReload: What a Change Does
devModeWatch: Watching Beans, Configuration and Resources
devModeTest: Continuous Testing
commandLineApps:
title: Standalone Command Line Applications
picocli:
title: Picocli Support
configurations:
title: Configurations
reactiveConfigs:
title: Configurations for Reactive Programming
reactor: Reactor Support
rxjava3: RxJava 3 Support
dataAccess:
title: Configurations for Data Access
sqlSupport: Configuring a SQL Data Source
hibernateSupport: Configuring Hibernate
mongoSupport: Configuring MongoDB
neo4jSupport: Configuring Neo4j
postgresSupport: Configuring Postgres
redisSupport: Configuring Redis
cassandraSupport: Configuring Cassandra
liquibaseSupport: Configuring Liquibase
flywaySupport: Configuring Flyway
logging:
title: Logging
loggingMessages: Logging Messages
loggingConfiguration: Configuration
logback: Logback
loggingSystem: Logging System
languageSupport:
title: Language Support
java:
title: Micronaut for Java
java9: Using Micronaut with Java 9+
incrementalannotationgradle: Incremental Annotation Processing with Gradle
lombok: Using Project Lombok
ide: Configuring an IDE
retainparameternames: Retaining Parameter Names
groovy: Micronaut for Groovy
python:
title: Micronaut for Python
pythonRuntime: Runtime Requirements
pythonThreading: Python Threading
decorators: Annotations as Decorators
sourceRoots: Main and Test Sources
typeOrdering: Type Ordering and Forward References
interfaces: Interfaces and Introduction Advice
standardTypes: Standard Library Type Conversions
javaCollections: Java Collections and Serialization
runtimeTypes: Python Classes and Objects at Run Time
javaImports: Importing Java Types
keywords: Python Keywords in Java APIs
nestedTypes: Nested Java Types
pythonExceptions: Python Exceptions Extending Java Exceptions
functionalInterfaces: Python Callables as Functional Interfaces
javaInterfaces: Implementing Java Interfaces
generatedClasses: Generated Java Classes
aopProxies: AOP Proxies
extendingJavaClasses: Extending Java Classes
typeChecking: Type Checking
moduleScripts:
title: Module Scripts
testing: JUnit 5 Module Tests
graalTools: GraalPy Tools
asyncAwait:
title: Async/Await
asyncioRuntime: Netty-Backed Asyncio Runtime
asyncioNetworking: Asyncio Networking and TLS
asyncioStreaming: Python-Native Streaming
asyncioUnsupported: Unsupported Asyncio APIs
kotlin:
title: Micronaut for Kotlin
kaptOrKsp: Kotlin support via KAPT or KSP
kapt: Kotlin Annotation Processing (KAPT)
ksp: Kotlin Symbol Processing (KSP)
controller: Controller in Kotlin
kaptintellij: Kotlin, Kapt and IntelliJ
gradlekapt: Incremental Annotation Processing with Gradle and Kapt
openandaop: Kotlin and AOP Advice
kotlinretainparamnames: Kotlin and Retaining Parameter Names
coroutines: Coroutines Support
coroutineTracingContextPropagation: Coroutine Tracing Context Propagation
kotlinContextPropagation: Reactive Context Propagation
graal:
title: Micronaut for GraalVM
graalServices: Microservices as GraalVM native images
graalFAQ: GraalVM and Micronaut FAQ
management:
title: Management & Monitoring
buildingEndpoints:
title: Creating Endpoints
endpointAnnotation: The Endpoint Annotation
endpointMethod: Endpoint Methods
endpointSensitivity: Endpoint Sensitivity
endpointConfiguration: Endpoint Configuration
providedEndpoints:
title: Built-In Endpoints
beansEndpoint: The Beans Endpoint
infoEndpoint: The Info Endpoint
healthEndpoint:
title: The Health Endpoint
healthEndpointConfiguration: Health Endpoint Configuration
healthEndpointStatusCode: Health Status Codes
healthLogging: Health Logging
healthEndpointCustomization: Custom Health Indicator
healthMonitoringTask: Health Monitor Task
healthProvidedIndicators:
title: Provided Indicators
diskSpaceHealthIndicator: Disk Space Health Indicator
jdbcHealthIndicator: JDBC Health Indicator
discoveryClientHealthIndicator: Discovery Client Health Indicator
deadlockedThreads: Deadlocked Threads
metricsEndpoint: The Metrics Endpoint
refreshEndpoint: The Refresh Endpoint
routesEndpoint: The Routes Endpoint
loggersEndpoint: The Loggers Endpoint
cachesEndpoint: The Caches Endpoint
stopEndpoint: The Server Stop Endpoint
environmentEndpoint: The Environment Endpoint
threadDumpEndpoint: The ThreadDump Endpoint
security:
title: Security
multitenancy:
title: Multi-Tenancy
cli:
title: Micronaut CLI
createProject:
title: Creating a Project
comparingVersions: Comparing Versions
features: Features
commands: Commands
reloading:
title: Reloading
automaticRestart: Automatic Restart
jrebel: JRebel
gradleReload: Recompiling with Gradle
ideReload: Recompiling with an IDE
proxy: Proxy Configuration
i18n:
title: Internationalization
bundle: Resource Bundles
localizedMessageSource: Localized Message Source
resources:
title: Resources
appendix:
title: Appendices
architecture:
title: Micronaut Architecture
compilerArch: Compiler
annotationArch: Annotation Metadata
introspectionArch: Bean Introspections
iocArch: Bean Definitions
aopArch: AOP Proxies
containerArch: Application Context
httpServerArch: HTTP Server
faq: Frequently Asked Questions (FAQ)
usingsnapshots: Using Snapshots
problems: Common Problems
breaks: Breaking Changes
Src Main Docs Guide Aop AdapterAdvice (`src/main/docs/guide/aop/adapterAdvice.adoc`)
There are cases where you want to introduce a new bean based on the presence of an annotation on a method. An example of this is the ann:runtime.event.annotation.EventListener[] annotation which produces an implementation of api:context.event.ApplicationEventListener[] for each annotated method that invokes the annotated method.
For example the following snippet runs the logic contained within the method when the api:context.ApplicationContext[] starts up:
[source,java]
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.event.annotation.EventListener;
...
@EventListener
void onStartup(StartupEvent event) {
// startup logic here
}
The presence of the ann:runtime.event.annotation.EventListener[] annotation causes the Micronaut framework to create a new class that implements api:context.event.ApplicationEventListener[] and invokes the onStartup method defined in the bean above.
The actual implementation of the ann:runtime.event.annotation.EventListener[] is trivial; it simply uses the ann:aop.Adapter[] annotation to specify which SAM (single abstract method) type it adapts:
[source,java]
include::context/src/main/java/io/micronaut/runtime/event/annotation/EventListener.java[tags=imports, indent=0]
include::context/src/main/java/io/micronaut/runtime/event/annotation/EventListener.java[tags=clazz, indent=0]
<1> The ann:aop.Adapter[] annotation indicates which SAM type to adapt, in this case api:context.event.ApplicationEventListener[].
NOTE: The Micronaut framework also automatically aligns the generic types for the SAM interface if they are specified.
Using this mechanism you can define custom annotations that use the ann:aop.Adapter[] annotation and a SAM interface to automatically implement beans for you at compile time.
=== The generated bean and the bean it adapts
The generated adapter is a bean in its own right, distinct from the bean that declares the adapted method. It
does nothing but delegate: when its SAM method is called it resolves the declaring bean and invokes the adapted
method on it.
Because of that, advice declared on the class that declares the adapted method is not applied to the generated
adapter. Such advice describes the declaring bean, and applying it to the adapter as well would run it twice for
what is logically a single target: once on the adapter's SAM method and again when the adapter delegates. It also
makes modules that keep per-target state, such as a lifecycle interceptor instance per object, see two targets
where there is one.
Advice declared on the adapted method itself is unaffected. It stays on the declaring bean, and it still runs when
the adapter delegates, so for instance ann:retry.annotation.Retryable[] on an ann:runtime.event.annotation.EventListener[]
method keeps retrying. Advice declared on the adapted SAM interface is kept on the adapter, since it describes the
adapter.
Src Main Docs Guide Aop AroundAdvice (`src/main/docs/guide/aop/aroundAdvice.adoc`)
The most common type of advice you may want to apply is "Around" advice, which lets you decorate a method's behaviour.
== Writing Around Advice
The first step is to define an annotation that will trigger a api:aop.MethodInterceptor[]:
snippet::io.micronaut.docs.aop.around.NotNull[tags="imports,annotation", indent=0, title="Around Advice Annotation Example"]
<1> The retention policy of the annotation should be RUNTIME
<2> Generally you want to be able to apply advice at the class or method level so the target types are TYPE and METHOD
<3> The ann:aop.Around[] annotation is added to tell the Micronaut framework that the annotation is Around advice
The next step to defining Around advice is to implement a api:aop.MethodInterceptor[]. For example the following interceptor disallows parameters with null values:
snippet::io.micronaut.docs.aop.around.NotNullInterceptor[tags="imports,interceptor", indent=0, title="MethodInterceptor Example"]
<1> The ann:aop.InterceptorBean[] annotation is used to indicate what annotation the interceptor is associated with. Note that ann:aop.InterceptorBean[] is meta-annotated with a default scope of @Singleton therefore if you want a new interceptor created and associated with each intercepted bean you should annotate the interceptor with ann:context.annotation.Prototype[].
<2> An interceptor implements the api:aop.MethodInterceptor[] interface.
<3> The passed link:{api}/io/micronaut/aop/MethodInvocationContext.html[MethodInvocationContext] is used to find the first parameter that is null
<4> If a null parameter is found an exception is thrown
<5> Otherwise link:{api}/io/micronaut/aop/InvocationContext.html#proceed--[proceed()] is called to proceed with the method invocation.
NOTE: Micronaut AOP interceptors use no reflection which improves performance and reducing stack trace sizes, thus improving debugging.
Apply the annotation to target classes to put the new MethodInterceptor to work:
snippet::io.micronaut.docs.aop.around.NotNullExample[tags="example", indent=0, title="Around Advice Usage Example"]
Whenever the type NotNullExample is injected into a class, a compile-time-generated proxy is injected that decorates method calls with the @NotNull advice defined earlier. You can verify that the advice works by writing a test. The following test verifies that the expected exception is thrown when the argument is null:
snippet::io.micronaut.docs.aop.around.AroundSpec[tags="test", indent=0, title="Around Advice Test"]
NOTE: Since Micronaut injection happens at compile time, generally the advice should be packaged in a dependent JAR file that is on the classpath when the above test is compiled. It should not be in the same codebase since you don't want the test to be compiled before the advice itself is compiled.
== Customizing Proxy Generation
The default behaviour of the link:{api}/io/micronaut/aop/Around.html[Around] annotation is to generate a proxy at compile time that is a subclass of the proxied class. In other words, in the previous example a compile-time subclass of the NotNullExample class will be produced where proxied methods are decorated with interceptor handling, and the original behaviour is invoked via a call to super.
This behaviour is more efficient as only one instance of the bean is required, however depending on the use case you may wish to alter this behaviour. The @Around annotation supports various attributes that allow you to alter this behaviour, including:
proxyTarget(defaults tofalse) - If set totrue, instead of a subclass that callssuper, the proxy delegates to the original bean instancehotswap(defaults tofalse) - Same asproxyTarget=true, but in addition the proxy implements link:{api}/io/micronaut/aop/HotSwappableInterceptedProxy.html[HotSwappableInterceptedProxy] which wraps each method call in aReentrantReadWriteLockand allows swapping the target instance at runtime.lazy(defaults tofalse) - By default the Micronaut framework eagerly initializes the proxy target when the proxy is created. If set totruethe proxy target is instead resolved lazily for each method call.
== AOP Advice on @Factory Beans
The semantics of AOP advice when applied to <<factories,Bean Factories>> differs from regular beans, with the following rules applying:
. AOP advice applied at the class level of a ann:context.annotation.Factory[] bean applies the advice to the factory itself and not to any beans defined with the ann:context.annotation.Bean[] annotation.
. AOP advice applied on a method annotated with a bean scope applies the AOP advice to the bean that the factory produces.
Consider the following two examples:
snippet::io.micronaut.docs.aop.advice.type.MyFactory[tags="class", indent=0, title="AOP Advice at the type level of a @Factory"]
The above example logs the time it takes to create the MyBean bean.
Now consider this example:
snippet::io.micronaut.docs.aop.advice.method.MyFactory[tags="class", indent=0, title="AOP Advice at the method level of a @Factory"]
The above example logs the time it takes to execute the public methods of the MyBean bean, but not the bean creation.
The rationale for this behaviour is that you may at times wish to apply advice to a factory and at other times apply advice to the bean produced by the factory.
Note that there is currently no way to apply advice at the method level to a ann:context.annotation.Factory[] bean, and all advice for factories must be applied at the type level. You can control which methods have advice applied by defining methods as non-public which do not have advice applied.
Src Main Docs Guide Aop Caching (`src/main/docs/guide/aop/caching.adoc`)
Like Spring and Grails, the Micronaut framework provides caching annotations in the link:{micronautcacheapi}/io/micronaut/cache/package-summary.html[io.micronaut.cache] package.
The link:{micronautcacheapi}/io/micronaut/cache/CacheManager.html[CacheManager] interface allows different cache implementations to be plugged in as necessary.
The link:{micronautcacheapi}/io/micronaut/cache/SyncCache.html[SyncCache] interface provides a synchronous API for caching, whilst the link:{micronautcacheapi}/io/micronaut/cache/AsyncCache.html[AsyncCache] API allows non-blocking operation.
== Cache Annotations
The following cache annotations are supported:
- link:{micronautcacheapi}/io/micronaut/cache/annotation/Cacheable.html[@Cacheable] - Indicates a method is cacheable in the specified cache
- link:{micronautcacheapi}/io/micronaut/cache/annotation/CachePut.html[@CachePut] - Indicates that the return value of a method invocation should be cached. Unlike
@Cacheablethe original operation is never skipped. - link:{micronautcacheapi}/io/micronaut/cache/annotation/CacheInvalidate.html[@CacheInvalidate] - Indicates the invocation of a method should cause the invalidation of one or more caches.
Using one of these annotations activates the link:{micronautcacheapi}/io/micronaut/cache/interceptor/CacheInterceptor[CacheInterceptor], which in the case of @Cacheable caches the return value of the method.
The emitted result is cached if the method return type is a non-blocking type (either link:{jdkapi}/java.base/java/util/concurrent/CompletableFuture.html[CompletableFuture] or an instance of rs:Publisher[]) .
In addition, if the underlying Cache implementation supports non-blocking cache operations, cache values are read without blocking, resulting in non-blocking cache operations.
== Configuring Caches
By default, https://github.com/ben-manes/caffeine[Caffeine] is used to create caches from application configuration. For example:
[configuration,title="Cache Configuration Example"]
micronaut:
caches:
my-cache:
maximum-size: 20
The above example configures a cache called "my-cache" with a maximum size of 20.
[NOTE] .Naming Caches
Define names of caches under `micronaut.caches` in kebab case (lowercase and hyphen separated); if you use camel case, the names are normalized to kebab case. For example `myCache` becomes `my-cache`. The kebab-case form must be used when referencing caches in the {micronautcacheapi}/io/micronaut/cache/annotation/Cacheable.html[@Cacheable] annotation.
To configure a weigher to be used with the maximumWeight configuration, create a bean that implements io.micronaut.caffeine.cache.Weigher. To associate a given weigher with only a specific cache, annotate the bean with @Named(<cache name>). Weighers without a named qualifier apply to all caches that don't have a named weigher. If no beans are found, a default implementation is used.
See the https://micronaut-projects.github.io/micronaut-cache/latest/guide/configurationreference.html#io.micronaut.cache.caffeine.DefaultCacheConfiguration[configuration reference] for all available configuration options.
== Dynamic Cache Creation
A {micronautcacheapi}/io/micronaut/cache/DynamicCacheManager.html[DynamicCacheManager] bean can be registered for use cases where caches cannot be configured ahead of time. When a cache is attempted to be retrieved that was not predefined, the dynamic cache manager is invoked to create and return a cache.
By default, if there is no other dynamic cache manager defined in the application, the Micronaut framework registers an instance of {micronautcacheapi}/io/micronaut/cache/caffeine/DefaultDynamicCacheManager.html[DefaultDynamicCacheManager] that creates Caffeine caches with default values.
== Other Cache Implementations
Check the https://micronaut-projects.github.io/micronaut-cache/latest/guide/index.html[Micronaut Cache] project for more information.
Src Main Docs Guide Aop InterceptorLifecycle (`src/main/docs/guide/aop/interceptorLifecycle.adoc`)
An api:aop.Interceptor[] is an ordinary bean, so its scope decides how many instances exist and how long each one
lives. That choice matters as soon as an interceptor keeps state, because the framework decides from it whether one
instance is shared by every intercepted bean or whether each intercepted bean gets its own.
== Interception Kinds
A single interceptor can be bound to more than one kind. The kind also decides which interceptor interface is
selected: only api:aop.ConstructorInterceptor[] beans are invoked for AROUND_CONSTRUCT, and only
api:aop.MethodInterceptor[] beans for the others. An interceptor that implements neither, that is a plain
api:aop.Interceptor[], is eligible for every kind it is bound to.
|===
|Kind |Applies to |Requires
|AROUND
|Every intercepted method of the bean
|ann:aop.Around[] on the target
|INTRODUCTION
|Every abstract method of the bean
|ann:aop.Introduction[] on the target
|AROUND_CONSTRUCT
|The bean's constructor
|ann:aop.AroundConstruct[] on the target
|POST_CONSTRUCT
|The bean's @PostConstruct methods
|@InterceptorBinding(kind = POST_CONSTRUCT) on the target
|PRE_DESTROY
|The bean's @PreDestroy methods
|@InterceptorBinding(kind = PRE_DESTROY) on the target
|===
== Interceptor Scope
ann:aop.InterceptorBean[] is meta-annotated with @Singleton, so unless you say otherwise an interceptor is a
singleton:
- Singleton β one instance for the whole application context, shared by every intercepted bean and every kind it is
bound to. It must not hold state that belongs to one intercepted bean, because it will see many. State that belongs to
one type can be cached against theExecutableMethodor declaring type from the api:aop.InvocationContext[]. - Non-singleton β declare ann:context.annotation.Prototype[], or another non-singleton scope, and the framework
creates one instance per intercepted bean. That instance is then reused for every kind bound to that bean, so state
set up while the bean is constructed is still there when the bean is destroyed.
.A per-target interceptor [source,java]
@Prototype // <1>
@InterceptorBinding(value = Tracked.class, kind = InterceptorKind.AROUND_CONSTRUCT)
@InterceptorBinding(value = Tracked.class, kind = InterceptorKind.POST_CONSTRUCT)
@InterceptorBinding(value = Tracked.class, kind = InterceptorKind.PRE_DESTROY)
public class TrackingInterceptor implements Interceptor<Object, Object> {
private Registration registration; // <2>
@Override
public Object intercept(InvocationContext<Object, Object> context) {
MethodInvocationContext<?, ?> method = (MethodInvocationContext<?, ?>) context;
if (method.getKind() == InterceptorKind.POST_CONSTRUCT) {
registration = Tracker.register(method.getTarget());
} else if (method.getKind() == InterceptorKind.PRE_DESTROY) {
registration.close(); // <3>
}
return context.proceed();
}}
<1> The interceptor overrides the default singleton scope, so one is created per intercepted bean
<2> State that belongs to a single intercepted bean, which a singleton interceptor could not hold safely
<3> The same instance handles @PreDestroy, so it can release what it acquired in @PostConstruct
NOTE: A lifecycle interception runs once per event, so a bean with several @PostConstruct methods in its class
hierarchy is intercepted once, and proceed() invokes all of them. See <<lifecycleAdvice, Bean Life Cycle Advice>>.
== Life Cycle of a Non-Singleton Interceptor
For one intercepted bean, in order:
. The interceptors bound to that bean are resolved once, while the bean is being created.
. AROUND_CONSTRUCT interception runs, then the bean's constructor.
. Once every AROUND_CONSTRUCT interceptor has returned, the bean's fields and methods are injected.
. POST_CONSTRUCT interception runs once, then the bean's @PostConstruct methods, superclass methods first.
. AROUND or INTRODUCTION interception runs on each method call, for as long as the bean lives.
. PRE_DESTROY interception runs once, then the bean's @PreDestroy methods, superclass methods first.
. The interceptor is destroyed as a dependent of the intercepted bean, so its own @PreDestroy runs after the
target's.
The same instance serves every step. A singleton interceptor follows the same sequence but the instance is shared with
every other intercepted bean, and it is destroyed with the context rather than with any one target.
Where the interceptors are held depends on the shape of the intercepted bean, which is an implementation detail but
explains the one case that behaves differently:
|===
|Target |Held by |Phases sharing one instance
|ann:aop.Around[] advice
|The generated proxy
|construct, methods, post-construct, pre-destroy
|@Around(proxyTarget = true), which includes every scoped proxy
|The generated proxy, for the methods; the target's own for its lifecycle, see <>
|methods
|@Around(proxyTarget = true, lazyInterceptorsPerTarget = true)
|The target's registration in the context
|construct, methods, post-construct, pre-destroy
|ann:aop.Introduction[] advice
|The generated proxy
|methods, post-construct, pre-destroy
|Bean produced by a ann:context.annotation.Factory[] method
|The generated proxy
|methods, post-construct, pre-destroy
|ann:aop.AroundConstruct[] advice with no ann:aop.Around[]
|The bean's registration in the context
|construct, post-construct, pre-destroy
|A ann:context.annotation.Prototype[] with ann:aop.AroundConstruct[] advice and no proxy, created with createBean and destroyed with destroyBean(Object)
|Nothing; the context tracks no registration for it
|construct and post-construct only
|===
NOTE: In the last row the context has no registration for the instance, so nothing links its destruction back to what
it was created with, and PRE_DESTROY resolves a new interceptor. A proxied bean created and destroyed the same way
is unaffected: the proxy retained its registrations and destroyBean(Object) destroys the non-singleton ones with the
target. Prototypes injected into other beans, and prototypes destroyed through their BeanRegistration, are unaffected.
[[proxiedTargets]]
=== Proxied Targets
A proxy that fronts a separate target, which is what @Around(proxyTarget = true) generates and what every scoped
proxy (ann:runtime.http.scope.RequestScope[], ann:runtime.context.scope.ThreadLocal[],
ann:runtime.context.scope.Refreshable[]) and every advised bean a ann:context.annotation.Factory[] method produces use,
is two beans to the context: the proxy and the target. By default the proxy resolves its interceptors once, as it is
constructed, and intercepts every target it fronts with them. A non-singleton interceptor is then one instance per
proxy, shared by every target the proxy resolves, and not the instance that intercepts the construction and
post-construct of the target. When the proxy is destroyed together with a target it holds, the target is destroyed
first: its pre-destroy is intercepted by the instance of the proxy, which is destroyed after it.
[.lang-java.lang-groovy.lang-kotlin]
Set lazyInterceptorsPerTarget to have the interceptors resolved for each target instead:
snippet::io.micronaut.docs.aop.lifecycle.pertarget.Audited[tags="imports,annotation", indent=0, title="Resolving interceptors for each target"]
A non-singleton interceptor is then the target's own: the instance created with the target, which also intercepts its
construction, post-construct and pre-destroy, or one created for it on its first call, as a dependent of the target's
registration. Two targets never share an instance, and each instance is destroyed with its target. A lazy proxy, such
as a scoped proxy, intercepts each target it resolves with that target's instances. The proxy itself is injected with no
interceptors and creates none.
Core records shared singleton interceptors as dependencies of the target, including interceptors selected after the
target was created. During context shutdown, a singleton target or a singleton owning a prototype target is destroyed
before those shared interceptors. Failed selections clean up newly created dependents, and a selection racing target
destruction cannot attach new interceptors to the destroyed target. Custom-scoped interceptors remain owned by their
scope.
Once context shutdown begins, new selections are only made on the thread running the shutdown, on behalf of a shutdown
event listener or a destruction callback, including the destruction callbacks of the target itself; the interceptors
they create are destroyed with the target. Selections already cached on a target remain available to destruction
callbacks until that target is destroyed.
An interceptor that resolves additional beans dynamically can inject api:context.BeanDependencyResolver[] to give
those dependencies its lifetime. It uses the same ownership and failure-cleanup infrastructure as native interceptor
selection. Ordinary prototype lookups still create separate instances; native interceptor selection separately retains
the instance associated with each target.
For example, this interceptor numbers the calls it intercepts:
snippet::io.micronaut.docs.aop.lifecycle.pertarget.AuditInterceptor[tags="imports,interceptor", indent=0, title="A per-target interceptor"]
<1> A non-singleton interceptor, so each target gets its own instance
<2> State that belongs to one target
On a ann:runtime.context.scope.Refreshable[] bean annotated with @Audited, each target the refresh creates starts
counting again:
snippet::io.micronaut.docs.aop.lifecycle.pertarget.AuditInterceptorSpec[tags="test", indent=0, title="Each target has its own interceptor"]
<1> One instance intercepts every method of the target
<2> The refresh replaces the target
<3> The new target is intercepted by an instance of its own
For a scoped bean whose binding annotation carries no ann:aop.Around[] of its own, declare the member together with
the settings of the scope's proxy, @Around(proxyTarget = true, lazy = true, lazyInterceptorsPerTarget = true): an@Around declared on the bean replaces the one its scope contributes.
NOTE: A target handed to a hot-swappable proxy through `swap` is intercepted with its own instances when the context holds a registration for it, a singleton or a bean of a custom scope. Any other object, such as the target of another proxy, is intercepted with instances the context creates once for such targets. A runtime proxy (ann:aop.runtime.RuntimeProxy[]) keeps the interceptors it was created with.
[.lang-python]
NOTE: An advised Python bean is proxied at run time, by a ann:aop.runtime.RuntimeProxy[], which keeps the interceptors it was created with, so a non-singleton interceptor is one instance per proxy.
[[interceptionTarget]]
=== Knowing the Target
A non-singleton interceptor created for one bean can inject api:context.InterceptionTarget[] to learn which bean
that target is. Its definition() is the definition of the intercepted bean, carrying the bean's qualifier, so two
targets of one class, such as two beans of one ann:context.annotation.EachBean[], are told apart:
snippet::io.micronaut.docs.aop.lifecycle.pertarget.labelled.Channel[tags="channel", indent=0, title="Two targets of one class"]
<1> One Channel for each configured channel, each with the name of its configuration as qualifier
snippet::io.micronaut.docs.aop.lifecycle.pertarget.labelled.LabelInterceptor[tags="imports,interceptor", indent=0, title="An interceptor that knows its target"]
<1> The bean this instance is created for, injected like any other dependency
<2> The qualifier of that bean, @Named("sms") for the sms channel
snippet::io.micronaut.docs.aop.lifecycle.pertarget.labelled.LabelInterceptorTest[tags="test", indent=0, title="Each target is labelled with its own qualifier"]
The target is known only to the interceptor's own injection points, not to the beans the interceptor depends on. It
is not known to a singleton or custom-scoped interceptor, which is shared, or to an interceptor obtained by a plain
lookup; an injection point declared @Nullable receives null in those cases and any other fails with aNoSuchBeanException.
[.lang-java.lang-groovy.lang-kotlin]
The target is known whenever the container creates the interceptor for a bean: while the bean itself is created, to intercept its construction, its lifecycle or, through the subclass or introduction proxy that is the bean, its methods, and when a proxy with a separate target and `lazyInterceptorsPerTarget` resolves the interceptors of a target on its first call. It is not known to the interceptors a proxy creates for a target the context holds no registration for. Without `lazyInterceptorsPerTarget` the interceptors of a proxy with a separate target are injected into the proxy as ordinary dependencies, shared by every target it fronts, and do not know the target either.
[.lang-python]
An advised Python bean is a runtime proxy, which creates its interceptors as the bean is created, so each one knows the bean without `lazyInterceptorsPerTarget`.
== Ordering
When several interceptors apply to the same interception point they run in ascending order, and the order is the same
in every kind. An interceptor controls its position by implementing io.micronaut.core.order.Ordered:
[source,java]
@Prototype
@InterceptorBinding(value = Tracked.class, kind = InterceptorKind.POST_CONSTRUCT)
public class FirstInterceptor implements Interceptor<Object, Object>, Ordered {
@Override
public int getOrder() {
return 10; // runs before an interceptor returning 20
}
@Override
public Object intercept(InvocationContext<Object, Object> context) {
return context.proceed();
}}
== Selection
An interceptor is selected for an interception point when its binding annotation matches the target's, and it is
bound to that kind. Binding to one kind does not imply the others, so an interceptor bound only to POST_CONSTRUCT
never runs for AROUND, and an interceptor bound only to AROUND never runs for a lifecycle phase. Interceptors bound
by a different annotation are never applied to the bean.
TIP: Reuse of one interceptor instance across the phases of a proxied bean is part of the generated proxy. After
upgrading, recompile intercepted classes so that beans compiled by an earlier version pick it up; until then they
behave as they did before, resolving an interceptor per interception point.
Src Main Docs Guide Aop IntroductionAdvice (`src/main/docs/guide/aop/introductionAdvice.adoc`)
Introduction advice is distinct from Around advice in that it involves providing an implementation instead of decorating.
Examples of introduction advice includes https://spring.io/projects/spring-data/[Spring Data] which implements persistence logic for you.
Micronaut api:http.client.annotation.Client[] annotation is another example of introduction advice where the Micronaut framework implements HTTP client interfaces for you at compile time.
The way you implement Introduction advice is very similar to how you implement Around advice.
You start by defining an annotation that powers the introduction advice. As an example, say you want to implement advice to return a stubbed value for every method in an interface (a common requirement in testing frameworks). Consider the following @Stub annotation:
snippet::io.micronaut.docs.aop.introduction.Stub[tags="imports,class", indent=0, title="Introduction Advice Annotation Example"]
<1> The introduction advice is annotated with api:aop.Introduction[]
<2> The api:context.annotation.Bean[] annotation is added so that all types annotated with @Stub become beans
The StubIntroduction class referred to in the previous example must then implement the api:aop.MethodInterceptor[] interface, just like around advice.
The following is an example implementation:
snippet::io.micronaut.docs.aop.introduction.StubIntroduction[tags="imports,class", indent=0, title="StubIntroduction"]
<1> The api:aop.InterceptorBean[] annotation is used to associate the interceptor with the @Stub annotation
<2> The class is annotated with @Singleton and implements the api:aop.MethodInterceptor[] interface
<3> The value of the @Stub annotation is read from the context and an attempt made to convert the value to the return type
<4> Otherwise null is returned
To now use this introduction advice in an application, annotate your abstract classes or interfaces with @Stub:
snippet::io.micronaut.docs.aop.introduction.StubExample[tags="class", indent=0, title="StubExample"]
All abstract methods delegate to the StubIntroduction class to be implemented.
The following test demonstrates the behaviour or StubIntroduction:
snippet::io.micronaut.docs.aop.introduction.IntroductionSpec[tags="test", indent=0, title="Testing Introduction Advice"]
Note that if the introduction advice cannot implement the method, call the proceed method of the api:aop.MethodInvocationContext[]. This lets other introduction advice interceptors implement the method, and an UnsupportedOperationException will be thrown if no advice can implement the method.
In addition, if multiple introduction advice are present you may wish to override the getOrder() method of api:aop.MethodInterceptor[] to control the priority of advice.
The following sections cover core advice types provided by Micronaut.
Src Main Docs Guide Aop LifecycleAdvice (`src/main/docs/guide/aop/lifecycleAdvice.adoc`)
Sometimes you may need to apply advice to a bean's lifecycle. There are 3 types of advice that are applicable in this case:
- Interception of the construction of the bean
- Interception of the bean's
@PostConstructinvocation - Interception of a bean's
@PreDestroyinvocation
The Micronaut framework supports these 3 use cases by allowing the definition of additional ann:aop.InterceptorBinding[] meta-annotations.
Consider the following annotation definition:
snippet::io.micronaut.docs.aop.lifecycle.ProductBean[tags="imports,class", indent=0, title="AroundConstruct example"]
<1> The ann:aop.AroundConstruct[] annotation is added to indicate that interception of the constructor should occur
<2> An ann:aop.InterceptorBinding[] definition is used to indicate that @PostConstruct interception should occur
<3> An ann:aop.InterceptorBinding[] definition is used to indicate that @PreDestroy interception should occur
<4> The bean is defined as ann:context.annotation.Prototype[] so a new instance is required for each injection point
Note that if you do not need @PostConstruct and @PreDestroy interception you can simply remove those bindings.
The @ProductBean annotation can then be used on the target class:
snippet::io.micronaut.docs.aop.lifecycle.Product[tags="imports,class", indent=0, title="Using an AroundConstruct meta-annotation"]
<1> The @ProductBean annotation is defined on a class of type Product
<2> The ann:context.annotation.Parameter[] annotation indicates that this bean requires an argument to complete constructions
<3> Any @PreDestroy or @PostConstruct methods are executed last in the interceptor chain
Now you can define api:aop.ConstructorInterceptor[] beans for constructor interception and api:aop.MethodInterceptor[] beans for @PostConstruct or @PreDestroy interception.
The following factory defines a api:aop.ConstructorInterceptor[] that intercepts construction of Product instances and registers them with a hypothetical ProductService validating the product name first:
snippet::io.micronaut.docs.aop.lifecycle.ProductInterceptors[tags="imports,class,constructor-interceptor", indent=0, title="Defining a constructor interceptor"]
<1> A new ann:aop.InterceptorBean[] is defined that is a api:aop.ConstructorInterceptor[]
<2> The constructor parameter values can be retrieved and modified as needed
<3> The constructor can be invoked with the proceed() method
The api:core.beans.BeanConstructor[] returned by getConstructor() describes the constructor being advised. For a
bean that is also proxied with @Around it describes the constructor of the intercepted class, with only the
parameters that class declares, rather than the generated proxy constructor. Its getTargetConstructor() returns
the corresponding java.lang.reflect.Constructor, the way getTargetMethod() does for an intercepted method; the
constructors the framework hands out resolve it once and hold it. It returns null when the bean is not created
through a constructor of its own type, as for a bean produced by a factory method.
Defining api:aop.MethodInterceptor[] instances that interceptor the @PostConstruct and @PreDestroy methods is no different from defining interceptors for regular methods. Note however that you can use the passed api:aop.MethodInvocationContext[] to identify what kind of interception is occurring and adapt the code accordingly like in the following example:
snippet::io.micronaut.docs.aop.lifecycle.ProductInterceptors[tags="method-interceptor", indent="0", title="Defining a constructor interceptor"]
<1> A new ann:aop.InterceptorBean[] is defined that is a api:aop.MethodInterceptor[]
<2> @PostConstruct interception is handled
<3> @PreDestroy interception is handled
=== What Construct Advice Wraps
Construct advice wraps the constructor call only. The instance that proceed() returns has been created, but its
fields and methods have not been injected yet and its @PostConstruct methods have not run. Both happen once every
api:aop.ConstructorInterceptor[] in the chain has returned, on the instance the chain returned:
. The construct interceptors run up to proceed(), outermost first
. The constructor runs, and any ann:context.annotation.InjectScope[] constructor argument is released
. The construct interceptors complete after proceed(), innermost first
. Fields and methods are injected
. @PostConstruct interception runs, then the @PostConstruct methods
An interceptor that needs the injected and initialized bean should bind to POST_CONSTRUCT instead, and an exception
thrown by a construct interceptor after proceed() means the instance is neither injected nor initialized. This is
the order the Jakarta Interceptors specification defines for @AroundConstruct.
NOTE: A bean compiled with a Micronaut version before 5.3.0 keeps the previous behaviour, in which injection and@PostConstruct ran inside the chain, until it is recompiled.
=== Exceptions Thrown by Construct Advice
An exception thrown by a api:aop.ConstructorInterceptor[] reaches whoever asked for the bean as it was thrown,
the same way an exception thrown by a api:aop.MethodInterceptor[] reaches the caller of the method it advises.
It is not wrapped in a api:context.exceptions.BeanInstantiationException[]. This holds whether the interceptor
throws before proceed(), rejecting the arguments the constructor was about to receive, or after it, rejecting
the instance the constructor produced:
[source,java]
@InterceptorBean(ProductBean.class)
class ProductInterceptor implements ConstructorInterceptor {
@Override
public Product intercept(ConstructorInvocationContext context) {
if (context.getParameterValues()[0] == null) {
throw new IllegalArgumentException("A product needs a name"); // <1>
}
return context.proceed();
}
}
<1> context.getBean(Product.class) throws this IllegalArgumentException, not a BeanInstantiationException
This lets advice reject a construction in its own terms. It is what allows constraints declared on a
constructor to be enforced when the container creates the bean, so that the caller catches the validation
exception rather than a wrapper around it.
An exception thrown by the constructor's own body is not advice throwing, and is wrapped in a
api:context.exceptions.BeanInstantiationException[] as it is for a bean with no construct advice at all. So is
any failure during eager initialization at startup, where the bean is not being created on anyone's behalf.
=== Intercepted Callbacks
A @PostConstruct or @PreDestroy interception runs once per lifecycle event of the bean, not once per callback.
The interceptor chain bound to the event runs first, and proceed() in the last interceptor invokes every callback
of that kind declared by the bean, superclass callbacks first, in the same order for both events. An interceptor that
does not call proceed() keeps all of them from running, and the bean it returns replaces the instance.
The api:aop.MethodInvocationContext[] passed to the interceptor stands for the event: getKind() isPOST_CONSTRUCT or PRE_DESTROY, getTarget() is the bean, and proceed() returns the bean.getExecutableMethod() stands for the whole event, and names it after the last callback the event will run: the
most derived one, which is the one the bean itself declares where it declares any. Its getDeclaringType() andgetMethodName() are that callback's, and getTargetMethod() resolves to that callback's Method; only when the
bean declares no callback of that kind does the name fall back to initialize or dispose. It still describes the
event rather than that one callback, so it carries the annotation metadata of the bean class and declares no
parameters, a callback's own arguments being resolved only once the event proceeds.
The callbacks the event is about to run are available from the bean definition of the intercepted bean:
api:inject.InitializingBeanDefinition[] exposes getPostConstructExecutableMethods() and
api:inject.DisposableBeanDefinition[] exposes getPreDestroyExecutableMethods(), each a list of reflection-free
api:inject.ExecutableMethod[] instances in invocation order. Every entry carries the annotation metadata of its
callback, for example the @PostConstruct annotation, and can be invoked on the target by an interceptor that needs
to run a callback itself. The lists are only populated for a bean that binds the corresponding interception kind.
The callbacks are not part of the bean definition's executable methods: BeanDefinition.getExecutableMethods() does
not include them, so executable method processors and adapters are not affected. A bean compiled by an earlier
version of the framework reports no callbacks.
An interceptor bound to these lifecycle kinds is a singleton by default, and one instance is then shared by every bean
it is applied to. To give each intercepted bean its own interceptor, so that state set up in @PostConstruct is still
available in @PreDestroy, see <>.
Src Main Docs Guide Aop Retry (`src/main/docs/guide/aop/retry.adoc`)
In distributed systems and microservice environments, failure is something you have to plan for, and it is common to want to attempt to retry an operation if it fails. If first you don't succeed try again!
With this in mind, the Micronaut framework includes a api:retry.annotation.Retryable[] annotation.
== Retry Dependency
NOTE: Since Micronaut Framework 4.0 to use the Retry functionality you need to add the following dependency:
dependency::micronaut-retry[]
== Simple Retry
The simplest form of retry is just to add the @Retryable annotation to a type or method. The default behaviour of @Retryable is to retry three times with a linear delay of one second between each retry. (first attempt with 1s delay, second attempt with 2s delay, third attempt with 3s delay).
For example:
snippet::io.micronaut.docs.aop.retry.BookService[tags="simple", indent=0, title="Simple Retry Example"]
With the above example if the listBooks() method throws an Exception, it is retried until the maximum number of attempts is reached.
The multiplier value of the @Retryable annotation can be used to configure a multiplier used to calculate the delay between retries, allowing exponential retry support.
To customize retry behaviour, set the attempts and delay members, For example to configure five attempts with a two seconds delay:
snippet::io.micronaut.docs.aop.retry.BookService[tags="attempts", indent=0, title="Setting Retry Attempts"]
Notice how both attempts and delay are defined as strings. This is to support configurability through annotation metadata. For example, you can allow the retry policy to be configured using property placeholder resolution:
snippet::io.micronaut.docs.aop.retry.BookService[tags="config", indent=0, title="Setting Retry via Configuration"]
With the above in place, if book.retry.attempts is specified in configuration it is bound to the value of the attempts member of the @Retryable annotation via annotation metadata.
== Reactive Retry
@Retryable advice can also be applied to methods that return reactive types, such as Publisher (https://projectreactor.io[Project Reactor]'s Flux or https://github.com/ReactiveX/RxJava[RxJava]'s Flowable). For example:
snippet::io.micronaut.docs.aop.retry.BookService[tags="reactive", indent=0, title="Applying Retry Policy to Reactive Types"]
In this case @Retryable advice applies the retry policy to the reactive type.
== Circuit Breaker
Retry is useful in a microservice environment, but in some cases excessive retries can overwhelm the system as clients repeatedly re-attempt failing operations.
The https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern[Circuit Breaker] pattern is designed to resolve this issue by allowing a certain number of failing requests and then opening a circuit that remains open for a period before allowing additional retry attempts.
The api:retry.annotation.CircuitBreaker[] annotation is a variation of the @Retryable annotation that supports a reset member which indicates how long the circuit should remain open before it is reset (the default is 20 seconds).
snippet::io.micronaut.docs.aop.retry.BookService[tags="circuit", indent=0, title="Applying CircuitBreaker Advice"]
The above example retries the findBooks method three times and then opens the circuit for 30 seconds, rethrowing the original exception and preventing potential downstream traffic such as HTTP requests and I/O operations flooding the system.
== Programmatic Retry
For cases where annotations are not desirable, Micronaut Retry also provides programmatic retry creation through typed policies and injected factory beans.
Create a policy once and derive reusable operations from the injected factory:
snippet::io.micronaut.docs.aop.retry.ProgrammaticBookService[tags="programmatic-policy", indent=0, title="Creating a Programmatic Retry Policy"]
The programmatic API uses typed values such as Duration and int instead of annotation strings.
For synchronous work, execute the supplier directly through RetryOperations:
snippet::io.micronaut.docs.aop.retry.ProgrammaticBookService[tags="programmatic-sync", indent=0, title="Programmatic Retry for Synchronous Code"]
Reactive retry is also supported. Pass a supplier so each retry attempt creates a fresh Publisher:
snippet::io.micronaut.docs.aop.retry.ProgrammaticBookService[tags="programmatic-reactive", indent=0, title="Programmatic Retry for Reactive Code"]
For asynchronous work, pass a supplier that creates a new CompletionStage for each attempt:
snippet::io.micronaut.docs.aop.retry.ProgrammaticBookService[tags="programmatic-async", indent=0, title="Programmatic Retry for Asynchronous Code"]
== Programmatic Circuit Breaker
Programmatic circuit breakers are created in the same way, using a typed CircuitBreakerPolicy and an injected CircuitBreakerOperationsFactory. The resulting CircuitBreakerOperations instance owns the shared breaker state.
snippet::io.micronaut.docs.aop.retry.ProgrammaticBookService[tags="programmatic-circuit", indent=0, title="Programmatic Circuit Breaker"]
== Named Retry Policies
NOTE: Named retry policies are experimental.
Retry settings shared by several methods can be configured once as a named policy under micronaut.retry.policies.<name>, and referenced with the name member of ann:retry.annotation.Retryable[]:
[configuration]
micronaut:
retry:
policies:
books:
attempts: 5
delay: 200ms
multiplier: 2
max-delay: 10s
jitter: 0.1
includes:
- java.io.IOException
excludes:
- java.io.FileNotFoundException
snippet::io.micronaut.docs.aop.retry.NamedRetryBookService[tags="named", indent=0, title="Applying a Named Retry Policy"]
<1> The method takes the attempts, delay, multiplier, maximum delay, jitter, includes and excludes of the books policy
As with the annotation, attempts is the maximum number of retries after the first call: attempts: 5 calls the method at most six times. The includes and excludes are fully qualified class names of exception types.
A member set explicitly on the annotation overrides that setting of the policy alone, e.g. includes on the annotation replaces the includes of the policy and keeps its excludes:
snippet::io.micronaut.docs.aop.retry.NamedRetryBookService[tags="override", indent=0, title="Overriding a Named Retry Policy"]
<1> One retry, so at most two calls, with the other settings of the books policy
A policy member that is not configured takes the default of ann:retry.annotation.Retryable[]. A name that is not configured fails each call of the method with an IllegalStateException naming the missing policy; a method returning a CompletionStage or a Publisher returns it failed. A policy that is invalid, e.g. with an include that is not the name of an exception class, fails the startup of the application. Without a name, the annotation behaves as before.
A ann:retry.annotation.CircuitBreaker[] method that has a name of ann:retry.annotation.Retryable[], e.g. on its class, retries with the named policy in the same way.
The policies are also available programmatically through the api:retry.RetryRegistry[] bean, whose retry(name) returns the RetryOperations of a policy.
== Named Circuit Breakers
A circuit breaker with a name is shared by everything that uses the name: the api:retry.CircuitBreakerRegistry[] gives its circuit to programmatic users, to every method annotated @CircuitBreaker(name = "...") and to other modules. They open, half-open and close one circuit together, while each user keeps its own retries. A @CircuitBreaker without a name keeps a circuit of its own, as before.
[configuration]
micronaut:
retry:
circuit-breakers:
inventory:
reset: 30s # the time the circuit stays open before it half-opens
attempts: 1 # the retries of registry.circuitBreaker("inventory")
snippet::io.micronaut.docs.aop.retry.InventoryService[tags="named", indent=0, title="Methods Sharing a Named Circuit Breaker"]
<1> The method retries twice, and a failure that survives its retries opens the inventory circuit
<2> The method fails fast once stock opened the circuit
snippet::io.micronaut.docs.aop.retry.InventoryClient[tags="registry", indent=0, title="Named Circuit Breakers from the Registry"]
<1> The operations of the inventory circuit, with the retries of its configuration
<2> The guard of the shipping circuit, see below
<3> The operation fails fast while the inventory circuit is open, whichever user opened it
The reset timeout and the rolling window of a named circuit are those of its configuration, and a user that declares others fails. A name that is not configured takes them from its users, which must all agree, the defaults included: registry.circuitBreaker(name) and registry.guard(name) have the defaults, and a user that disagrees with the one that created the circuit fails with an IllegalStateException naming both. Configure a name that users share once, rather than repeating its settings.
A caller that runs each operation itself and only reports its outcome uses the api:retry.CircuitBreakerGuard[] of the name: acquire() fails fast with a CircuitOpenException while the circuit is open, onSuccess() and onFailure(e) report the outcome, and release() returns a permit whose operation ended without one, for example when it was cancelled. A guard never retries, so a caller can report an outcome as soon as it knows it, for example once the headers of a response arrive, without waiting for its body.
snippet::io.micronaut.docs.aop.retry.InventoryClient[tags="guard", indent=0, title="Reporting Outcomes with a CircuitBreakerGuard"]
<1> Fails fast with a CircuitOpenException while the circuit is open
<2> Reports a success
<3> Reports a failure, which may open the circuit
Without further settings, as for any circuit breaker of Micronaut, the first failure that survives the retries opens the circuit, and a half-open circuit closes on the first success and opens again on the first failure.
=== Rolling Window
A circuit breaker may instead decide by a rolling window of outcomes, as in MicroProfile Fault Tolerance. Setting any of these, on @CircuitBreaker or under micronaut.retry.circuit-breakers.<name>, gives the circuit a window, with the defaults of the others. Programmatically, a api:retry.CircuitBreakerWindow[] built with CircuitBreakerWindow.builder() is passed with the policy to registry.circuitBreaker(name, policy, window):
|===
|Setting |Default |Meaning
|requestVolumeThreshold (request-volume-threshold) |20 |the number of the last calls of a closed circuit in the window
|failureRatio (failure-ratio) |0.5 |the ratio of failures of a full window that opens the circuit; never before the window is full
|successThreshold (success-threshold) |1 |the trial calls of a half-open circuit: all succeed to close it, any failure opens it again, and the other calls are rejected without counting
|failOn (fail-on) |every exception |the exceptions that count as a failure
|skipOn (skip-on) |none |the exceptions that count as a success, whatever failOn says
|===
The reset timeout (reset) is the time an open circuit waits before it half-opens; a half-open circuit whose trial calls have not all reported within it, for example because they were abandoned, permits new ones. The outcome of a call of an annotated method is its outcome after its retries, and an exception its excludes or predicate do not retry counts as a success. A call that started before the circuit last changed state counts for nothing, and a reactive call counts once it is subscribed: cancelled before it produced a value, it counts for nothing. CircuitBreakerRegistry.findSnapshot(name) gives the state and counters of a circuit.
For example, the shipping circuit of the guard above opens when two of its last four calls failed, and a NoSuchElementException counts as a success:
[configuration]
micronaut:
retry:
circuit-breakers:
shipping:
reset: 30s
request-volume-threshold: 4
failure-ratio: 0.5
success-threshold: 2
skip-on:
- java.util.NoSuchElementException
The same window on the annotation:
snippet::io.micronaut.docs.aop.retry.PricingService[tags="window", indent=0, title="A Rolling Window on @CircuitBreaker"]
<1> One retry: a call counts once in the window, with its outcome after the retry
<2> The window holds the last four calls, and the circuit never opens before it is full
<3> Two failures of four calls open the circuit
<4> Two trial calls of the half-open circuit must succeed to close it
<5> A NoSuchElementException counts as a success
== Factory Bean Retry
When ann:retry.annotation.Retryable[] is applied to bean factory methods, it behaves as if the annotation was placed on the type being returned. The retry behavior applies when the methods on the returned object are invoked. Note that the bean factory method itself is not retried. If you want the functionality of creating the bean to be retried, it should be delegated to another singleton that has the ann:retry.annotation.Retryable[] annotation applied.
For example:
[source,java]
@Factory // <1>
public class Neo4jDriverFactory {
...
@Retryable(ServiceUnavailableException.class) // <2>
@Bean(preDestroy = "close")
public Driver buildDriver() {
...
}
}
<1> A factory bean is created that defines methods that create beans
<2> The ann:retry.annotation.Retryable[] annotation is used to catch exceptions thrown from methods executed on the Driver.
== Retry Events
You can register api:retry.event.RetryEventListener[] instances as beans to listen for api:retry.event.RetryEvent[] events that are published every time an operation is retried.
In addition, you can register event listeners for api:retry.event.CircuitOpenEvent[] to be notified when a circuit breaker circuit is opened, or api:retry.event.CircuitClosedEvent[] for when a circuit is closed.
Src Main Docs Guide Aop Scheduling (`src/main/docs/guide/aop/scheduling.adoc`)
Like Spring and Grails, the Micronaut framework features a api:scheduling.annotation.Scheduled[] annotation for scheduling background tasks.
TIP: See the guide for https://guides.micronaut.io/latest/micronaut-scheduled.html[Schedule Periodic Tasks inside your Micronaut Applications] to learn more.
== Using the @Scheduled Annotation
The api:scheduling.annotation.Scheduled[] annotation can be added to any method of a bean, and you should set one of the fixedRate, fixedDelay, or cron members. Scheduling requires the Micronaut Context dependency:
dependency::micronaut-context[]
micronaut-context is a transitive dependency of micronaut-http. If you use a Micronaut HTTP runtime, your project already includes the Micronaut-context dependency.
NOTE: Remember that the scope of a bean impacts behaviour. A @Singleton bean shares state (the fields of the instance) each time the scheduled method is executed, while for a @Prototype bean a new instance is created for each execution.
=== Scheduling at a Fixed Rate
To schedule a task at a fixed rate, use the fixedRate member. For example:
snippet::io.micronaut.docs.aop.scheduled.ScheduledExample[tags="fixedRate", indent=0, title="Fixed Rate Example"]
The task above executes every five minutes.
=== Scheduling with a Fixed Delay
To schedule a task, so it runs five minutes after the termination of the previous task use the fixedDelay member. For example:
.Fixed Delay Example
snippet::io.micronaut.docs.aop.scheduled.ScheduledExample[tags="fixedDelay", indent=0, title="Fixed Delay Example"]
=== Scheduling a Cron Task
To schedule a https://en.wikipedia.org/wiki/Cron[Cron] task use the cron member:
snippet::io.micronaut.docs.aop.scheduled.ScheduledExample[tags="cron", indent=0, title="Cron Example"]
The above example runs the task every Monday morning at 10:15AM in the time zone of the server.
=== Scheduling with only an Initial Delay
To schedule a task, so it runs once after the server starts, use the initialDelay member:
snippet::io.micronaut.docs.aop.scheduled.ScheduledExample[tags="initialDelay", indent=0, title="Initial Delay Example"]
The above example only runs once, one minute after the server starts.
=== Reading the Schedule That Triggered a Call
The arguments of a scheduled method are resolved from the bean context. A parameter of type api:scheduling.ScheduledExecution[] instead receives the invocation itself: the scheduled method and the ann:scheduling.annotation.Scheduled[] declaration that triggered the call, which for a method with several schedules tells which one fired. The same value is available for the duration of the call, including to interceptors of the method, from ScheduledExecution.current(), which is empty when the application calls the method directly.
== Programmatically Scheduling Tasks
To programmatically schedule tasks, use the api:scheduling.TaskScheduler[] bean which can be injected as follows:
snippet::io.micronaut.docs.aop.scheduled.TaskSchedulerInjectExample[tags="inject", indent=0]
== Configuring Scheduled Tasks with Annotation Metadata
To make your application's tasks configurable, you can use annotation metadata and property placeholder configuration. For example:
snippet::io.micronaut.docs.aop.scheduled.ScheduledExample[tags="configured", indent=0, title="Allow tasks to be configured"]
The above example allows the task execution frequency to be configured with the property my.task.rate, and the initial delay to be configured with the property my.task.delay.
== Configuring the Scheduled Task Thread Pool
Tasks executed by @Scheduled are run by default on a link:{jdkapi}/java.base/java/util/concurrent/ScheduledExecutorService.html[ScheduledExecutorService] configured to have twice the number of threads as available processors.
You can configure this thread pool in your configuration file (e.g application.yml):
//TODO: Move YAML snippet to ExecutorServiceConfigSpec [configuration,title="Configuring Scheduled Task Thread Pool"]
micronaut:
executors:
scheduled:
type: scheduled
core-pool-size: 30
include::{includedir}configurationProperties/io.micronaut.scheduling.executor.UserExecutorConfiguration.adoc[]
== Handling Exceptions
By default, the Micronaut framework includes a api:io.micronaut.scheduling.DefaultTaskExceptionHandler[] bean that implements the api:io.micronaut.scheduling.TaskExceptionHandler[] interface and simply logs the exception if an error occurs invoking a scheduled task.
If you have custom requirements you can replace this bean with your own implementation (for example to send an email or shutdown the context to fail fast). To do so, write your own api:io.micronaut.scheduling.TaskExceptionHandler[] and annotate it with @Replaces(DefaultTaskExceptionHandler.class).
Src Main Docs Guide Aop SpringAop (`src/main/docs/guide/aop/springAop.adoc`)
Although the Micronaut framework's design is based on a compile-time approach and does not rely on Spring dependency injection, there is still a lot of value in the Spring ecosystem that does not depend directly on the Spring container.
You may wish to use existing Spring projects within the Micronaut framework and configure beans to be used within the Micronaut framework.
You may also wish to leverage existing AOP advice from Spring. One example of this is Spring's support for declarative transactions with @Transactional.
The Micronaut framework provides support for Spring-based transaction management without requiring Spring itself. Simply add the spring module to your application dependencies:
dependency:io.micronaut.spring:micronaut-spring[gradleScope="implementation"]
This also requires adding the spring-annotation module dependency as an annotation processor:
dependency:io.micronaut.spring:micronaut-spring-annotation[scope="annotationProcessor"]
NOTE: If you use Micronaut's <<hibernateSupport,Hibernate support>> you already get this dependency and a HibernateTransactionManager is configured for you.
This is done by intercepting method calls annotated with Spring's @Transactional with link:{micronautspringapi}/io/micronaut/spring/tx/annotation/TransactionInterceptor.html[TransactionInterceptor].
The benefit here is you can use Micronaut's compile-time, reflection-free AOP to declare programmatic Spring transactions. For example:
.Using @Transactional [source,java]
import org.springframework.transaction.annotation.Transactional;
...
@Transactional
public Book saveBook(String title) {
...
}
Src Main Docs Guide Aop Validation (`src/main/docs/guide/aop/validation.adoc`)
Validation advice is one of the most common advice types you are likely to want to use in your application.
Validation advice is built on https://beanvalidation.org/2.0/spec/[Bean Validation JSR 380], a specification of the Java API for bean validation which ensures that the properties of a bean meet specific criteria, using jakarta.validation annotations such as @NotNull, @Min, and @Max.
The Micronaut framework provides native support for the jakarta.validation annotations with the micronaut-validation dependency:
dependency:micronaut-validation-processor[groupId="io.micronaut.validation",scope="annotationProcessor"]
dependency:micronaut-validation[groupId="io.micronaut.validation"]
Or full https://beanvalidation.org/2.0/spec/[JSR 380] compliance with the micronaut-hibernate-validator dependency:
dependency::micronaut-hibernate-validator[groupId="io.micronaut.beanvalidation"]
See the section on <> for more information on how to apply validation rules to your bean classes.
Src Main Docs Guide Appendix Architecture (`src/main/docs/guide/appendix/architecture.adoc`)
The following documentation describes the Micronaut framework's architecture and is designed for those who are looking for information on the internal workings of the Micronaut framework and how it is architected. This is not intended as end-user developer documentation, but for those interested in the inner workings of the Micronaut framework.
This documentation is divided into sections that describe the compiler, introspections, application container, dependency injection and so on.
WARNING: Since this documentation covers the internal workings of Micronaut, many APIs referenced and described are regarded as internal, non-public API and are annotated as such with the ann:core.annotation.Internal[]. Internal APIs can change between patch releases of Micronaut and are not covered by Micronaut's semantic versioning release policy.
Src Main Docs Guide Appendix Breaks (`src/main/docs/guide/appendix/breaks.adoc`)
This section documents breaking changes between Micronaut versions
=== Buffered JSON stream responses require a list type (5.3)
The JDK HTTP client now uses the shared JSON stream message body reader. For an application/x-json-stream response, retrieve(request, Book.class) no longer returns only the first JSON value. Retrieve Argument.listOf(Book.class) to read the complete stream, or use jsonStream(request, Book.class) to consume individual values as they arrive.
=== The application context is built with the loader of the main class (5.3)
Micronaut.run(Application.class), and any ApplicationContextBuilder.mainClass(...), now builds the context with the class loader of the main class when that loader is the loader of micronaut-inject or one of its descendants, unless a class loader was set explicitly with classLoader(...), by the caller or by an api:context.ApplicationContextConfigurer[]. Previously the builder always used the loader of micronaut-inject. This only changes anything for an application whose main class is loaded by a child of that loader, for example by a plugin or deployment class loader: its beans, configuration files and services are now found through that loader. A main class of a parent loader, or of a loader beside it, still leaves the loader of micronaut-inject.
=== The shared bean introspector indexes the introspections of each class loader once (5.3)
The introspections that the shared api:core.beans.BeanIntrospector[] finds through a class loader other than its own, the thread context loader when micronaut.introspections.use.context.classloader is set or the loader of a bean type, are now indexed once per loader instead of on every lookup. An introspection that a class loader only makes visible after the first lookup through it, for example in a jar added to a URLClassLoader later, is not found until that loader is invalidated, which only a development launcher does.
=== FileChangedEvent paths are absolute, and changes are coalesced and delayed (5.3)
The api:scheduling.io.watch.DefaultWatchThread[] that publishes a api:scheduling.io.watch.event.FileChangedEvent[] for the directories of micronaut.io.watch.paths now registers them with the api:scheduling.io.watch.FileWatcher[] of the context, which changes the events it publishes:
- The path of an event is absolute and normalized. Previously it was the path the watch service reports, relative to the directory the file is in, for example
application.ymlrather than/home/me/app/src/main/resources/application.yml. A listener that compares the path with a relative one should resolve that one first, for example withPath.toAbsolutePath().normalize(). - Events are coalesced. Several events for the same path in quick succession are published as one: a file created and then modified as
CREATE, a file modified and then deleted asDELETE, a file deleted and created again asMODIFY, and a file created and deleted again is not published at all. - Events are published once no further change was seen for the quiet period,
micronaut.io.watch.quiet-period(120 milliseconds by default), instead of as soon as the watch service reports them, so that the events of one save are published together.
The api:scheduling.io.watch.FileWatcher[] is a bean whenever a watch service is, that is unless micronaut.io.watch.enabled is false, and no longer requires micronaut.io.watch.paths. Nor does api:scheduling.io.watch.FileWatchConfiguration[], so that the check interval and the quiet period apply to the file watcher without watch paths; a bean that required the configuration to tell whether watch paths are set should require micronaut.io.watch.paths instead. The watch service bean, created by api:scheduling.io.watch.WatchServiceFactory[] (or the factory of micronaut-runtime-osx), no longer requires the paths either; it is only created when it is injected, and the file watcher only obtains it, and starts its thread, when a component first registers a directory. The constructors of DefaultWatchThread and MacOsWatchThread that take a WatchService, and the methods getWatchService(), registerPath(Path) and closeWatchService() they use, are deprecated: the thread registers with the file watcher instead.
=== The built-in load balancers look up the discovery clients without a Reactor chain (5.3)
The built-in load balancers and composite clients implement the new experimental AsyncLoadBalancer, AsyncDiscoveryClient and AsyncConfigurationClient interfaces, and DefaultAsyncFunctionDiscoveryClient serves the @FunctionClient methods that return a CompletionStage or a value. They call the DiscoveryClient beans that do not implement AsyncDiscoveryClient through their publishers, with a plain subscriber. When such a publisher is a Reactor publisher, its Reactor context holds the propagated context of the request, but no longer the other entries of the Reactor context of the caller. The LoadBalancer, DiscoveryClient, ConfigurationClient and FunctionDiscoveryClient interfaces are unchanged, and a load balancer that does not implement AsyncLoadBalancer is selected through its publisher as before.
FunctionInvoker has an experimental invokeAsync, which @FunctionClient methods that return a CompletionStage call, and which HttpFunctionExecutor implements with the asynchronous HTTP client. Such a method that returns CompletionStage<Void> now completes with null, where it failed with a FunctionNotFoundException, and HttpFunctionExecutor.invoke with a CompletionStage type returns the future of invokeAsync.
=== The management endpoints call the CompletionStage methods of their SPIs (5.3)
HealthIndicator, HealthAggregator, InfoSource, InfoAggregator and LoggersManager have experimental CompletionStage counterparts of their publisher methods (getResultAsync, aggregateAsync, getSourceAsync, getLoggersAsync and getLoggerAsync), which adapt the publisher methods by default. The health, info and loggers endpoints call them, and still return a single result Publisher.
DefaultHealthAggregator,ReactiveInfoAggregator,DiscoveryClientHealthIndicatorandJdbcIndicatorcombine the stages without a publisher. A subclass of them is called through its publisher methods, includingaggregateResults, so that its overrides keep working.- The results of the indicators are aggregated in the order of the indicators rather than in the order they complete. When two results have the same name, the one of the later indicator is kept.
- A mock indicator, source, aggregator or loggers manager that stubs only the publisher methods does not run the default
*Asyncmethods: a Spock or Mockito mock returnsnull, or a stage completed withnull, and the endpoints then call the publisher methods instead. - An indicator that throws from
getResult()fails the aggregation, rather than throwing out ofaggregate(..).
=== Refreshing the Logback configuration follows Logback's own lookup (5.3)
When any logger.* property is set, the Micronaut framework resets Logback and configures it again. Previously that refresh used the first Configurator service regardless of its rank and of the status it returned, and otherwise the file set with logback.configurationFile or logger.config, or logback.xml. Now, unless a Logback XML configuration file is set in Micronaut configuration, the refresh configures Logback the same way Logback configures itself at startup:
logback-test.xmltakes precedence overlogback.xml, and startup no longer fails when there is nologback.xml: Logback falls back to its basic console configuration.Configuratorservices run by rank. AConfiguratorthat returnsNEUTRALorINVOKE_NEXT_IF_ANYis followed by the XML configuration file. AConfiguratorthat must replace the XML configuration should returnDO_NOT_INVOKE_NEXT_IF_ANY.- The lookup uses the class loader that loaded Logback, as at Logback's startup, and no longer the one that loaded the Micronaut framework. That applies to the
Configuratorservices, tologback-test.xmlandlogback.xml, and to alogback.configurationFileJVM system property that names a classpath resource. - Logback resolves the
logback.configurationFileJVM system property: as a URL, then on the classpath, then on the file system. Previously it was looked up on the classpath and then on the file system, so a URL such asfile:/etc/app/logback.xmlfailed the startup, and now it works. When the value is not a URL and no such file exists, or it is a directory, startup no longer fails: Logback falls back tologback-test.xml,logback.xmlor its basic console configuration, and it does not uselogger.config. - When Logback cannot read or parse the file it finds, be it the one set with the
logback.configurationFileJVM system property, orlogback-test.xmlorlogback.xmlon the classpath, startup no longer fails with aLoggingSystemException: Logback reports the error the way it does at startup.
A logback.xml file in the working directory is still used when nothing else selects a configuration, that is, when there is no Configurator service, the logback.configurationFile JVM system property is not set, and neither logback-test.xml nor logback.xml is on the classpath. Previously that file was also used when only logback-test.xml was on the classpath, and now logback-test.xml takes precedence over it. The framework reads that file itself, so startup still fails with a LoggingSystemException when it cannot be parsed, unlike for the files that Logback finds.
A file set with logger.config, or with logback.configurationFile in Micronaut configuration unless its value is exactly that of the JVM system property of the same name, now takes precedence over any Configurator service, and startup still fails when it does not exist or cannot be parsed. The logging system of the loggers management endpoint now honors logback.configurationFile as well.
That logging system, io.micronaut.management.endpoint.loggers.impl.LogbackLoggingSystem, is created by the Micronaut framework when the loggers endpoint is enabled. Its public constructor, which reads only logger.config, is deprecated for removal, and with null it no longer defaults to logback.xml: the refresh then configures Logback the way Logback configures itself at startup. Do not construct the class: obtain the bean from the application context, for example by injecting LoggingSystem or ManagedLoggingSystem, or replace it with a bean of your own that implements both, annotated with @Replaces(LogbackLoggingSystem.class), rather than extending it.
=== The event streams of the HTTP client follow the Server-Sent Events specification (5.3)
The eventStream methods of api:http.client.sse.SseClient[] in the Netty client now decode the body as the https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation[event stream interpretation] of the Server-Sent Events specification, like exchangeEventStream and the server-sent events of the api:http.client.AsyncStreamingHttpClient[]. The body is still read as an event stream whatever the content type of the response, and an error status still fails the stream. A subscriber can observe these changes:
- A comment line, such as a
: pingheartbeat, is ignored. Previously a comment followed by a blank line was emitted as an event with empty data. - The
idof an event carries over to the events that follow it until anotheridline, as the last event id of the specification. Previously only the event with theidline had an id. - The name of an
event:line and the value of anid:line are no longer trimmed: only the one space after the colon is removed. - A
dataline without a colon adds an empty line to the data of the event, and a blank line after an event without data no longer fails the stream with aNullPointerException: an event without data is not emitted. - A carriage return alone ends a line, as a line feed or a carriage return followed by a line feed does. A leading byte order mark is skipped, an
idcontaining a null character and aretrythat is not a number are ignored. - When the request accepts an event stream,
dataStreamandexchangeStreamsplit the body into lines at a carriage return alone as well. - The line limit (
micronaut.http.client.max-content-length) applies to each line and to the data of one event. Exceeding it failseventStreamwith a api:http.client.exceptions.ContentLengthExceededException[], anHttpClientException, instead of anHttpClientException("Error consuming Server Sent Events") that wraps aBufferLengthExceededException.
=== Construct advice no longer wraps injection and @PostConstruct (5.3)
A api:aop.ConstructorInterceptor[] now wraps only the constructor call. Fields and methods of the bean are injected, and
its @PostConstruct methods run, after every construct interceptor has returned, instead of inside the call toproceed(). As a result @PostConstruct no longer runs before an outer construct interceptor has completed, nor when
one throws after proceed().
A construct interceptor that used the injected state of the instance returned by proceed() should bind toInterceptorKind.POST_CONSTRUCT instead, where the bean is injected. See <<lifecycleAdvice, Bean Life Cycle Advice>>.
Beans compiled with an earlier version keep the previous order until they are recompiled.
=== Routes with a variable without a regular expression are ordered first (5.3)
Routes are ordered by the length of the literal parts of their URI templates, then by the number of path variables. Since Micronaut 5.3 a third key follows: among routes that tie on both, the route with fewer path variables constrained by a regular expression is ordered first, so @Get("/items/{id}") sorts before @Get("/items/{id:.+}"). A numeric modifier such as {id:3} limits the length of the value and is not a regular expression: it is not counted. The key only orders routes, it does not select one: a GET /items/1 request matched by both routes still fails with a api:web.router.exceptions.DuplicateRouteException[] (answered with 400 Bad Request), as before.
The change is in UriTemplateMatcher.compareTo, and with it in the natural order (compareTo) of api:web.router.UriRouteInfo[] and its default implementation DefaultUrlRouteInfo, so the order of routes sorted with it changes too. Routes also have a new UriRouteInfo.getOrder() (0 by default and for every controller route): it is consulted after the produced and consumed media types, the specificity of the templates, the preference of a route of the request's method over a route of any method and of an explicit HEAD route over an implicit one, and only decides between routes that would otherwise be ambiguous.
=== A filter method that changes the URI of its MutableHttpRequest parameter changes the request (5.3)
A request filter method without a continuation can declare a api:http.MutableHttpRequest[] parameter. The server request itself cannot be changed, so the method receives a mutable copy of it: headers changed on the copy are applied to the request, but everything else only changes the copy.
Previously, changing the URI of that copy, e.g. request.uri(URI.create("/v2/items")) in a void filter method, had no effect unless the method returned the copy.
Since Micronaut 5.3, when the method changes the URI of the copy and returns nothing, the rest of the chain continues with the copy:
- the filters after it and the route see the new URI;
- for a api:http.server.annotation.PreMatching[] filter, the route is matched with the new URI;
- the body is still read from the original request;
- for a filter that returns a
PublisherorCompletionStage, the URI is checked when that result completes.
Changes to the query parameters or the body of the copy are still only kept when the method returns the copy.
If an earlier filter continued with a request that cannot be copied this way, for example an HttpRequestWrapper, the parameter is now a mutable wrapper of that request. Previously the method failed with an UnsupportedOperationException.
=== The response filters of a failed write are those of the route that answered (5.3)
When writing a response fails before it is committed, the error is handled by the error routes and the error response is filtered again by the response filters. Since Micronaut 5.3 those filters are selected with the route that answered the request, as for the response of that route, and include the filters of that route, rather than with the error route that handled the failure. A filter bound to a route with a ann:http.annotation.FilterMatcher[] annotation, or by its route attributes, filters the error response of its route when the write fails.
=== A CodecException thrown while writing the response body is handled by the error routes (5.3)
When a api:http.body.MessageBodyWriter[] failed with a api:http.codec.CodecException[] while writing the body of a response, for example because the body could not be serialized to JSON, the server previously answered the default error response (500 Internal Server Error, with the message of the exception when micronaut.server.error-response-include-message allows it) directly. The exception handlers, the error and status routes and the response filters did not see the failure.
Since Micronaut 5.3 a CodecException thrown before anything was sent is handled like any other exception of the writer: it goes to the exception handlers and the error and status routes, and the response filters run on the error response. An application with an exception handler or an error route for CodecException, or for a supertype such as Exception, or with a status route for 500, now gets its response instead of the default error response, which therefore no longer carries the message of the codec failure. The default error response is still answered when no handler or route applies, and when the writer of the error response fails as well.
=== The name condition of SoftServiceLoader.load is tested on every service entry (5.3)
The Predicate<String> given to the three-argument load(Class, ClassLoader, Predicate) of api:core.io.service.SoftServiceLoader[] is documented to be passed the class name of each service. Previously it was only tested on the lines of META-INF/services files, comment included. Since Micronaut 5.3 it is tested on the name of every entry, so a caller that passes a condition can get different results:
- Entries under
META-INF/micronaut, and the entries of the service table of a native image, are no longer returned when the condition rejects them. Previously the condition was not tested on them. - With a static service loader registered for the service type (the internal
SoftServiceLoader.StaticServiceLoader, which nothing in Micronaut 5 registers),collectAll()only returns the entries that the condition accepts. Previously it ignored the condition. - With such a loader,
iterator(), andfirst(),firstAvailable()andfirstOr(..), which use it, now return the entries that the condition accepts. Previously the condition was tested onjava.lang.Stringinstead of the name of each entry, so for most conditions the iterator was empty. - For a
META-INF/servicesline with a trailing comment, such ascom.example.Foo # comment, the condition is tested oncom.example.Foo. Previously it was tested on the whole line.
The name of a META-INF/services entry is now read without the whitespace around it, as java.util.ServiceLoader reads an entry that is padded with spaces or tabs. This also applies without a condition. Previously an indented entry, or one followed by whitespace, was not loaded: collectAll() left it out, and iterator(), first() and firstOr(..) returned it as a definition that is not present, under the name with its whitespace. For a line that has only whitespace, or only whitespace before a comment, they returned a definition that is not present, with a blank name. Now such an entry is returned under its class name, and such a line is skipped.
Callers of the load methods without a condition are otherwise not affected.
=== Executor beans propagate the context without micronaut-context-propagation (5.3)
The instrumentation that makes ExecutorService beans run submitted tasks with the api:core.propagation.PropagatedContext[] of the submitting thread moved from micronaut-context-propagation to micronaut-context. Applications that use micronaut-runtime or micronaut-http already had it. Applications that only depend on micronaut-context, for example messaging applications, now get it too. It covers the io, blocking, virtual and scheduled executors, executors configured under micronaut.executors.*, and @Async and @ExecuteOn methods. See <<contextPropagation, Context propagation>>.
A task captures the context when it is submitted, so a task that submits or schedules more work on an executor bean passes its context on. To keep the previous behaviour for one executor, set propagate-context: false on it, for example micronaut.executors.scheduled.propagate-context: false.
Periodic tasks, scheduled with scheduleAtFixedRate or scheduleWithFixedDelay, no longer capture the context, because they would keep it alive until they are cancelled. This also applies to applications that use micronaut-runtime or micronaut-http, where these tasks previously ran with the context of the code that scheduled them. To keep that, wrap the task with PropagatedContext.wrapCurrent(..) when scheduling it.
Executors created from micronaut.executors.* are now instrumented when they are created, before any BeanCreatedEventListener of ExecutorService runs. The context propagation is therefore the innermost wrapper, and other executor instrumentations run with the context on the worker thread. Previously, an instrumentation ordered before the context propagation instrumentation ran without it. The listener still instruments other ExecutorService beans.
=== A reflectively dispatched executable method propagates the method's exception
An executable method that generated code cannot call directly, for example a private method annotated with @Executable and @ReflectiveAccess, is dispatched through reflection. Previously ExecutableMethod.invoke wrapped whatever such a method threw in an api:core.reflect.exception.InvocationException[], with the java.lang.reflect.InvocationTargetException as its cause, while the same method declared package-private or public threw its own exception.
A reflectively dispatched method now propagates the method's exception unchanged, checked exceptions included, just as a directly dispatched method does. The same applies to an interceptor that invokes one of its own private executable methods, and to a private @PostConstruct or @PreDestroy callback of an intercepted bean. An InvocationException is still thrown when the reflective call itself fails before the method runs, for example on illegal access. Bean introspections are not affected: a private property accessor dispatched through reflection still wraps what it threw in an InvocationException.
Code that caught InvocationException to unwrap the cause should catch the method's exception instead. Ordinary executable method dispatch changes for classes compiled with Micronaut 5.2.1 or later: bean definitions compiled with an earlier version keep the previous behavior until they are recompiled. Intercepted lifecycle callbacks are the exception, because the AOP runtime unwraps the exception itself: an interceptor of a private @PostConstruct or @PreDestroy callback sees the callback's exception, checked exceptions included, even when the bean definition was compiled with an earlier version.
=== HTTP client redirect forwarding no longer retains authorization headers cross-origin by default
The Netty HTTP client no longer forwards Authorization, Proxy-Authorization, or Cookie headers on cross-origin redirects by default.
This applies to both normal redirects and preserve-body redirects (307 and 308).
If needed, this behavior can be customized through HttpClientConfiguration.
=== Header values may no longer contain characters above U+00FF
SimpleHttpHeaders (and the CaseInsensitiveMutableHttpHeaders behind it, used when Netty is not on the classpath) now rejects a header value containing a character above U+00FF with an IllegalArgumentException.
RFC 7230 defines a header value over octets β field-vchar = VCHAR / obs-text, where obs-text is %x80-FF β so a character that is not representable as a single octet is not a valid header value. Previously such a value was accepted and then handled inconsistently by whichever transport sent it: Netty substitutes ? for each offending character, while the JDK HTTP client throws from its own request builder. The value is now rejected where it is set, naming the header and the offending index.
Characters in %x80-FF are unaffected and remain valid, so a Content-Disposition filename using Latin-1 accented characters continues to work. Only characters above U+00FF β for example CJK text or emoji β are rejected, and those were already being replaced with ? on the wire.
== 5.0
=== Core Changes
==== Duplicate configuration resources now fail fast by default
In Micronaut Framework 5, if a configuration file such as application.properties or application.yml is present more than once on the classpath, Micronaut now fails fast by default with a api:io.micronaut.context.exceptions.ConfigurationException[] describing the conflicting locations.
See <<duplicateConfigurationResources, Duplicate Configuration Resources>> for more options (including merging duplicates).
To restore the previous behavior (first match wins), configure the application context builder:
snippet::io.micronaut.docs.context.env.ConfigurationLoadStrategySnippet[tags="restoreFirstMatch",indent=0,title="Restoring FIRST_MATCH behavior"]
==== Adoption of the IANA standard for YAML media types
Micronaut now uses official application/yaml media type for YAML format. Before it was application/x-yaml.
See https://www.rfc-editor.org/rfc/rfc9512.html for details.
==== Update to Jackson 3
Micronaut Jackson Databind uses https://github.com/FasterXML/jackson/wiki/Jackson-Release-3.0[Jackson 3].
If you use Micronaut Jackson Databind, check the https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md[Jackson 3 Migration Guide].
Micronaut configuration properties for various Jackson features have been renamed. Please check the https://docs.micronaut.io/latest/guide/configurationreference.html#io.micronaut.jackson.JacksonConfiguration[configuration reference]. Also note that Jackson has renamed a few features upstream, and that various defaults have changed.
==== Update to Apache Groovy 5
Micronaut 5 uses http://groovy-lang.org/releasenotes/groovy-5.0.html[Apache Groovy 5].
Check https://groovy-lang.org/releasenotes/groovy-5.0.html#Groovy5.0-breaking[Groovy 5 breaking changes].
==== JSpecify Nullability Annotations
The Micronaut APIs use https://jspecify.dev[JSpecify] annotations, and we recommend users to embrace JSpecify nullability annotations.
==== Bean context changes
The default implementations of api:context.BeanContext[BeanContext], api:context.ApplicationContext[ApplicationContext] and api:context.env.Environment[Environment] are no longer public and non-final.
There are new options added to api:context.ApplicationContextBuilder[ApplicationContextBuilder] which allow modifying the behavior of the bean context without overriding it:
[source,java]
ApplicationContext myBootstrapContext = ApplicationContext.builder()
.deducePackage(false)
.deduceEnvironment(false)
.eventsEnabled(false)
.eagerBeansEnabled(false)
.beansPredicate(reference -> reference.isAnnotationPresent(BootstrapContextCompatible.class))
.build();
Alternatively there is a new way how to create api:context.env.Environment[Environment]:
[source,java]
Environment myEnv = Environment.create(new ApplicationContextConfiguration() {
@Override
public List
return List.of("foobar");
}
});
ApplicationContext customApplicationContext = ApplicationContext.create(myEnv);
==== Bean context performance improvements
The bean context in v5 tries to avoid scanning all available beans as much as possible.
The bean definitions are now generated with all posible exposed types (if not explicitly defined by @Bean(typed=..)).
Beans added at the runtime require all the exposed types (supertypes and interfaces) explicitly defined:
[source,java]
class FooBar extends Abc implements Resolver
}
// In v4 this registration will resolve beans by class FooBar, Abc and Resolver
beanContext.registerSingleton(new FooBar());
// In v5 it is necessary to register it in the following way:
context.registerBeanDefinition(
RuntimeBeanDefinition.builder(new FooBar())
.singleton(true)
.exposedTypes(FooBar.class, Abc.class, Resolver.class)
.typeArguments(Resolver, Argument.of(String))
.build()
);
==== Runtime annotation processors changes
The @Executable methods processor api:context.processor[ExecutableMethodProcessor] should be used only to process the executable methods that should be processed at startup. The annotation should be annotated with @Executable(processOnStartup = true). For other use-cases (executable or not) api:context.processor[BeanDefinitionProcessor] should be used.
The incorrect use will result in a warning and an error in the next major version.
Both interfaces are revisited to have better generics and no longer have now deleted AnnotationProcessor superclass.
Executable methods annotated with @Parallel will not trigger parallel execution of api:context.processor[ExecutableMethodProcessor]. Users who relied on that functionality should handle parallelism separately.
@Scheduled is no longer processed in parallel, this reduced the complexity of the method processor and removed unnecessary overhead.
==== HTTP server thread selection now applies across more request stages
Micronaut 5 extends server thread-selection handling so that it applies not only to route execution, but also to server filters and request event listeners.
As a result, applications that previously observed different threads for filters, event listeners, and controllers may now see those stages executed more consistently on the executor selected by the server configuration.
The micronaut.server.netty.redispatch-non-blocking-only setting now controls whether Micronaut redispatches again after request processing has already moved from the initial non-blocking event-loop thread to a blocking-capable thread:
trueskips repeated redispatch once execution is already on a blocking-capable thread. This reduces executor hops and avoids creating additional virtual threads for every filter, event listener, and controller stage.falseapplies the configured executor at each stage even if the current thread is already blocking-capable. This more closely matches the previous behavior, but may introduce more thread hops and, when using virtual threads, more virtual thread creation during one request.
If your application depends on specific thread transitions between filters, event listeners, and route handlers, review this setting and test request-processing behavior after upgrading.
==== Jackson Bean Introspection Module removed
NOTE: This change only affects users using <<serializationUsingJacksonDatabind, Micronaut Jackson Databind>>
Previous versions of Micronaut Core contained a so-called "bean introspection module". This Jackson module hooked into jackson-databind to replace reflective field and method access with Micronaut introspection-based calls. The performance difference is negligible, but the module allows serialization in native images without having to add reflection metadata.
In version 5, we removed this module because it is difficult to maintain. Native image users now have a good alternative to <<serializationUsingJacksonDatabind, Micronaut Jackson Databind>> in https://micronaut-projects.github.io/micronaut-serialization/latest/guide[Micronaut Serialization].
Users who do not wish to use Micronaut Serialization will have to add reflection metadata for the objects they serialize.
If you want to keep using <<serializationUsingJacksonDatabind, Micronaut Jackson Databind>>, beware that deserialization of such class works in Micronaut 4:
[source, java]
package example;
import java.util.Objects;
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}}
In Micronaut 5, you will need to add @JsonProperty annotations to the constructor parameters. This is standard Jackson databind deserialization behavior.
[source, java]
package example;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class Person {
private final String name;
private final int age;
public Person(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}}
To generate a GraalVM Native Image, where you need to de/serialize such as class, you can add the following dependency:
dependency:micronaut-graal[scope="annotationProcessor",groupId="io.micronaut.graal"]
and annotate it with api:core.annotation.ReflectiveAccess[]. Read <<graal, Graal>> section of the Micronaut core documentation.
[source, java]
package example;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.core.annotation.ReflectiveAccess;
@ReflectiveAccess
public class Person {
private final String name;
private final int age;
public Person(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}}
== 4.0.0
=== Core Changes
==== Further Micronaut Modularization
The micronaut-runtime module has been split into separate modules depending on the application's use case:
===== Micronaut Discovery Core
micronaut-discovery-core - The base service discovery features are now a separate module. If your application listens for events such as api:discovery.event.ServiceReadyEvent[] or api:health.HeartBeatEvent[] this module should be added to the application classpath.
dependency::micronaut-discovery-core[]
===== Micronaut Retry
micronaut-retry - The retry implementation including annotations such as ann:retry.annotation.Retryable[] is now a separate module that can be optionally included in a Micronaut application.
In addition, since micronaut-retry is now optional declarative clients annotated with ann:http.client.annotation.Client[] no longer invoke fallbacks by default. To restore the previous behaviour add micronaut-retry to your classpath and annotate any declarative clients with ann:retry.annotation.Recoverable[].
To use the Retry functionality, add the following dependency:
dependency::micronaut-retry[]
==== Calling registerSingleton(bean) no longer overrides existing beans
If you call registerSingleton(bean) on the api:context.BeanContext[] it will no longer override existing beans if the type and qualifier match; instead, two beans will exist which may lead to a api:context.exceptions.NonUniqueBeanException[].
If you require replacing an existing bean you must formalize the replacement using the api:context.RuntimeBeanDefinition[] API, for example:
[source,java]
context.registerBeanDefinition(
RuntimeBeanDefinition.builder(Codec.class, ()-> new OverridingCodec())
.singleton(true)
// the type of the bean to replace
.replaces(ToBeReplacedCodec.class)
.build()
);
==== WebSocket No Longer Required
io.micronaut:micronaut-http-server no longer exposes micronaut-websocket transitively. If you are using annotations such as ann:websocket.annotation.ServerWebSocket[], you should add the micronaut-websocket dependency to your application classpath:
dependency::micronaut-websocket[]
==== Reactor Instrumentation Moved to Reactor Module
The instrumentation features for Reactor have been moved to the micronaut-reactor module. If you require instrumentation of reactive code paths (for distributed tracing for example) you should make sure your application depends on micronaut-reactor:
dependency:micronaut-reactor[groupId="io.micronaut.reactor"]
==== Validation Support Moved to Validation Module
The validation features link:{micronautvalidationdocs}[have been moved to a separate module]. Moreover, the new validation module requires you to use micronaut-validation-processor in the annotation processor classpath.
dependency:micronaut-validation-processor[groupId="io.micronaut.validation",scope="annotationProcessor"]
dependency:micronaut-validation[groupId="io.micronaut.validation"]
==== Session Support Moved to Session Module
The Session handling features link:{micronautsessiondocs}[have been moved to their own module]. If you use the HTTP session module, change the maven coordinates from io.micronaut:micronaut-session to io.micronaut.session:micronaut-session.
dependency:micronaut-session[groupId="io.micronaut.session"]
==== Kotlin Flow Support Moved to Kotlin Module
Support for the Kotlin Flow type has been moved to the micronaut-kotlin module. If your application uses Kotlin Flow you should ensure the micronaut-kotlin-runtime module is on your application classpath:
dependency:micronaut-kotlin-runtime[groupId="io.micronaut.kotlin"]
==== Compilation Time API Split into new module
In order to keep the runtime small all types and interfaces that are used at compilation time only (like the io.micronaut.inject.ast API) have been moved into a separate module:
dependency:micronaut-core-processor[]
If you are using types and interfaces from this module you should take care to split the compilation time and runtime logic of your module into separate modules.
==== ASM No Longer Shaded
https://asm.ow2.io/[ASM] is no longer shaded into the io.micronaut.asm package. If you depend on this library you should directly depend on the latest version of ASM.
==== Caffeine No Longer Shaded
https://github.com/ben-manes/caffeine[Caffeine] is no longer shaded into the io.micronaut.caffeine package. If you depend on this library you should directly depend on the latest version of Caffeine.
==== Environment Deduction Disabled by Default
In previous versions of the Micronaut framework, probes were used to attempt to deduce the running environment and establish whether the application was running in the Cloud. These probes involved network calls resulting in issues with startup performance and security concerns. These probes are disabled by default and can be re-enabled as necessary by calling ApplicationContextBuilder.deduceCloudEnvironment(true), setting the system property micronaut.env.cloud-deduction to true or setting the environment MICRONAUT_ENV_CLOUD_DEDUCTION to true if your application still requires this functionality.
==== Update to Groovy 4
Micronaut now uses Groovy 4.
This means that Groovy 4 is now the minimum version required to run Groovy Micronaut applications.
There have been several core differences in Groovy parsing and behavior for version 4 which can be found in the breaking changes section of the https://groovy-lang.org/releasenotes/groovy-4.0.html[4.0.0 release notes].
==== SnakeYAML no longer a direct dependency
SnakeYAML is no longer a direct dependency, if you need YAML configuration you should add SnakeYAML to your classpath explicitly
==== javax.annotation no longer a directory dependency
The javax.annotation library is no longer a directory dependency. Any references to types in the javax.anotation package should be changed to jakarta.annotation
==== Kotlin base version updated to 1.8.21
Kotlin has been updated to 1.8.21, which may cause issues when compiling or linking to Kotlin libraries.
==== Bean Introspection changes
Before, when both METHOD and FIELD were set as the access kind, the bean introspection would choose the same access type to get and set the property value. In Micronaut 4, the accessors can be of different kinds: a field to get and a method to set, and vice versa.
==== Annotations with retention CLASS are excluded at runtime
Annotations with the retention CLASS are not available in the annotation metadata at the runtime.
==== Interceptors with multiple interceptor bindings annotations
Interceptors with multiple interceptor binding annotations now require the same set of annotations to be present at the intercepted point. In the Micronaut 3 an interceptor with multiple binding annotations would need at least one of the binding annotations to be present at the intercepted point.
==== ConversionService and ConversionService.SHARED is no longer mutable
New type converters can be added to api:core.convert.MutableConversionService[] retrieved from the bean context or by declaring a bean of type api:core.convert.TypeConverter[].
To register a type converter into ConversionService.SHARED, the registration needs to be done via the service loader.
==== ExceptionHandler with POJO response type no longer results in an error response
Previously if you had an ExceptionHandler such as:
[source,java]
@Singleton
public class MyExceptionHandler implements ExceptionHandler<MyException, String> {
@Override
public String handle(HttpRequest request, MyException exception) {
return "caught!";
}}
This would result in an internal server error response with caught! as the body.
This now returns an OK response.
If you want to return a POJO response as an error, you should use the HttpResponse type:
[source,java]
@Singleton
public class MyExceptionHandler implements ExceptionHandler<MyException, HttpResponse
@Override
public HttpResponse<String> handle(HttpRequest request, MyException exception) {
return HttpResponse.badRequest("caught!");
}}
==== HttpContentProcessor superseded by MessageBodyHandler API
The netty-specific HttpContentProcessor API has been replaced by a new, experimental MessageBodyHandler API that
does not rely on netty and is more powerful. There is no compatibility layer, so the old HttpContentProcessor will stop
working and need to be rewritten.
==== @Body annotation on controller parameters
Before 4.0, the binding logic for controller parameters was more lax. A bare parameter, e.g. void test(String title),
could either match a part of the request body (foo if the request body is {"title":"foo"}), come from a query
parameter, or could bind to the full request body ({"x":"y"} if the request body is {"x":"y"}).
Binding from the full body to these bare parameters is no longer supported. If you wish to bind the full body, the
parameter must be annotated with @Body.
Additionally, it is no longer permitted to mix body component binding with full body binding. For example,void test(@Body Bean bean, String title) will not work anymore if title needs to come from the
body that is already bound to bean.
These changes also apply to functions that are exposed using micronaut-function-web.
==== Delayed body access
When accessing the request body in two places, for example once as a normal controller @Body parameter and then in an
error handler, Micronaut HTTP is now stricter about allowed types. If in doubt, for the second body access, callHttpRequest.getBody() and you will get the same body type the first access requested.
==== text/plain messages are more restrictive about allowed types
For text/plain request and response body reading and writing, in 3.x any type was allowed. For writing, the object
was converted using toString, and for reading, the object was converted using ConversionService. For
example, if you have a controller that returns an Instant as text/plain, it would write it using toString like2023-05-25T13:25:02.925Z. In the other direction, if you have a controller with a @Body Instant instant
parameter, the same text would be converted to Instant using ConversionService.
This is not permitted anymore for 4.x, except for some restricted types. The recommended fix is to move toapplication/json as the content type. toString is not a stable serialization format, JSON is more reliable.
Alternatively, you can set the micronaut.http.legacy-text-conversion configuration option to true to restore the
old behavior.
==== OncePerRequestHttpServerFilter removed
Since Micronaut 3.0 the OncePerRequestHttpServerFilter class was deprecated and marked for removal. This class is now removed. Implement link:{api}/io/micronaut/http/filter/HttpServerFilter.html[HttpServerFilter] instead, and replace any usages of micronaut.once attributes with a custom attribute name.
==== CORS support with the @CrossOrigin annotation
Micronaut Framework 4 changes @CrossOrigin behavior to match configuration-based CORS behavior. A method annotated with @CrossOrigin allows any origin if you don't specify any value for the allowedOrigins and allowedOriginsRegex members.
Micronaut Framework 5 changes the default value of @CrossOrigin.allowCredentials from true to false. If your application relies on credentialed cross-origin requests, set allowCredentials = true explicitly.
==== @EachBean requires a @Named qualifier
@EachBean throws a "multiple possible bean candidates found" exception if any parent bean lacks a name qualifier.
==== Manual Context Propagation
In Micronaut Framework 4, users need to extend the <<contextPropagation, propagation context>> manually.
==== Micronaut 3 libraries not compatible with Micronaut 4 applications
In order for Micronaut 3 library beans to be discoverable in an application running Micronaut 4, the library must be recompiled with Micronaut 4 - https://github.com/micronaut-projects/micronaut-core/discussions/9758
==== @Retryable Default Exception Type
The default exception type used by @Retryable has changed.
Previously, @Retryable would retry only when a RuntimeException (or subclass) was thrown.
In Micronaut 5, the default has been changed to Exception. As a result, checked exceptions will now also trigger retry behavior.
If the previous behavior is desired, configure the retryable annotation explicitly:
[source,java]
@Retryable(includes = RuntimeException.class)
==== Kotlin suspend routes honour the selected executor
In Micronaut Framework 4, a Kotlin suspend route ignored ann:scheduling.annotation.ExecuteOn[] and the micronaut.server.thread-selection setting: the route ran on the event loop and resumed on Dispatchers.Default. In Micronaut 5 the selected executor is applied, and it is also the dispatcher the coroutine resumes on.
This changes which thread a suspend route runs on for applications that annotate one with ann:scheduling.annotation.ExecuteOn[], or that set micronaut.server.thread-selection to AUTO, IO or BLOCKING (the default is MANUAL, which selects no executor and is unaffected). To keep a suspend route on the event loop under AUTO, annotate it with ann:core.annotation.NonBlocking[].
==== JDK HTTP client pipeline options
The JDK HTTP client preserves its existing redirect, request-timeout, and error-body behaviour by default. The following experimental options enable the corresponding shared-pipeline behaviour:
micronaut.http.client.jdk.apply-request-timeout: applies the configuredrequest-timeoutto the entire exchange.micronaut.http.client.jdk.use-micronaut-redirects: uses Micronaut redirect handling, including its redirect limit and method rewriting rules.micronaut.http.client.jdk.decode-error-type: decodes error responses using the exchange's error type.
All three options default to false. The JDK client also exposes toAsyncStreaming() for pulled JSON, byte, and event streams.
Src Main Docs Guide Appendix Faq (`src/main/docs/guide/appendix/faq.adoc`)
The following section covers frequently asked questions that you may find yourself asking while considering to use or using Micronaut.
==== Does Micronaut modify my bytecode?
No. Your classes are your classes. Micronaut does not transform classes or modify the bytecode generated from the code you write. Micronaut produces additional classes at compile time in the same package as your original unmodified classes.
==== Why Doesn't Micronaut use Spring?
When asking why Micronaut doesn't use Spring, it is typically in reference to the Spring Dependency Injection container.
NOTE: The Spring ecosystem is very broad and there are many Spring libraries you can use directly in Micronaut without requiring the Spring container.
The reason Micronaut features its own native https://www.jcp.org/en/jsr/detail?id=330[JSR-330] compliant dependency injection is that the cost of these features in Spring (and any reflection-based DI/AOP container) is too great in terms of memory consumption and the impact on startup time. To support dependency injection at runtime, Spring:
- https://github.com/spring-projects/spring-framework/tree/master/spring-core/src/main/java/org/springframework/core/type/classreading[Reads the bytecode] of every bean it finds at runtime.
- https://github.com/spring-projects/spring-framework/blob/a691065d05741a4f1ca17925c8a5deec0f378c8b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java#L1465[Synthesizes new annotations] for each annotation on each bean method, constructor, field etc. to support Annotation metadata.
- https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java[Builds Reflective Metadata] for each bean for every method, constructor, field, etc.
The result is a progressive degradation of startup time and memory consumption as your application incorporates more features.
For Microservices and Serverless functions where it is critical that startup time and memory consumption remain low, the above behaviour is an undesirable reality of using the Spring container, hence the designers of Micronaut chose not to use Spring.
==== Does Micronaut support Scala?
Micronaut supports any JVM language that supports the Annotation Processor API. Scala currently does not support this API. However, Groovy also doesn't support this API and special support has been built that processes the Groovy AST. It may be technically possible to support Scala in the future if a module similar to inject-groovy is built, but as of this writing Scala is not supported.
==== Can Micronaut be used for purposes other than Microservices?
Yes. Micronaut is very modular, and you can choose to use just the Dependency Injection and AOP implementation by including the micronaut-inject-java (or micronaut-inject-groovy for Groovy) dependency in your application.
In fact Micronaut's support for <<serverlessFunctions, Serverless Computing>> uses this exact approach.
==== What are the advantages of Micronaut's Dependency Injection and AOP implementation?
Micronaut processes your classes and produces all metadata at compile time. This eliminates the need for reflection, cached reflective metadata, and the requirement to analyze your classes at runtime, all of which lead to slower startup performance and greater memory consumption.
In addition, Micronaut builds reflection-free AOP proxies at compile time, which improves performance, reduces stack trace sizes, and reduces memory consumption.
==== Why does Micronaut have its own Consul and Eureka client implementations?
The majority of Consul and Eureka clients that exist are blocking and include many external dependencies that inflate your JAR files.
Micronaut's api:discovery.DiscoveryClient[] uses Micronaut's native HTTP client, greatly reducing the need for external dependencies and providing a reactive API onto both discovery servers.
==== Why am I encountering a NoSuchMethodError occurs loading my beans (Groovy)?
Groovy by default imports classes in the groovy.lang package, including one named @Singleton, an AST transformation class that makes your class a singleton by adding a private constructor and static retrieval method. This annotation is easily confused with the jakarta.inject.Singleton annotation used to define singleton beans in Micronaut. Make sure you use the correct annotation in your Groovy classes.
==== Why is it taking much longer than it should to start the application
Micronaut's startup time is typically very fast. At the application level however, it is possible to affect startup time. If you are seeing slow startup, review any application startup listeners or @Context scope beans that are slowing startup.
Some network issues can also cause slow startup. On the Mac for example, misconfiguration of your /etc/hosts file can cause issues. See the following link:https://stackoverflow.com/a/39698914/1264846[stackoverflow answer].
Src Main Docs Guide Appendix Problems (`src/main/docs/guide/appendix/problems.adoc`)
The following section covers common problems developers encounter when using Micronaut.
==== Dependency injection is not working
The most common causes of Dependency Injection failing to work are not having the appropriate annotation processor configured, or an incorrectly configured IDE. See the section on <<languageSupport, Language Support>> for how to get setup in your language.
==== A NoSuchMethodError occurs loading beans (Groovy)
By default, Groovy imports classes in the groovy.lang package which includes a class called Singleton. This is an AST transformation annotation that makes your class a singleton by adding a private constructor and static retrieval method. This annotation is easily confused with the jakarta.inject.Singleton annotation used to define singleton beans in Micronaut. Make sure you use the correct annotation in your Groovy classes.
==== It is taking much longer to start my application than it should (*nix OS)
This is likely due to a bug related to java.net.InetAddress.getLocalHost() calls causing a long delay. The solution is to edit your /etc/hosts file to add an entry containing your host name. To find your host name, run hostname in a terminal. Then edit your /etc/hosts file to add or change entries like the example below, replacing <hostname> with your host name.
127.0.0.1 localhost
::1 localhost
To learn more about this issue, see this link:https://stackoverflow.com/a/39698914/1264846[stackoverflow answer]
Src Main Docs Guide Appendix Usingsnapshots (`src/main/docs/guide/appendix/usingsnapshots.adoc`)
Micronaut milestone and stable releases are distributed to Maven Central.
The following snippet shows how to use Micronaut SNAPSHOT versions with Gradle. The latest snapshot will always be the next patch version plus 1 with -SNAPSHOT appended. For example if the latest release is "1.0.1", the current snapshot would be "1.0.2-SNAPSHOT".
[source, groovy]
ext {
micronautVersion = '4.10.0-SNAPSHOT'
}
repositories {
mavenCentral() // <1>
maven {
url = "https://central.sonatype.com/repository/maven-snapshots/" // <2>
mavenContent {
snapshotsOnly()
}
}
}
<1> Micronaut releases are available on Maven Central
<2> Micronaut snapshots are available on Central Sonatype maven-snapshots repository.
In the case of Maven, edit pom.xml:
[source, xml]
...
<groupId>io.micronaut.platform</groupId>
<artifactId>micronaut-parent</artifactId>
<version>4.10.0-SNAPSHOT</version>
<micronaut.version>4.10.0-SNAPSHOT</micronaut.version>
......
<1> Set the snapshot version.
<2> Micronaut snapshots are available on Sonatype.
Src Main Docs Guide Cli Commands (`src/main/docs/guide/cli/commands.adoc`)
You can view a full list of available commands using the help flag, for example:
[source,bash]
$ mn -h
Usage: mn [-hvVx] [COMMAND]
Micronaut CLI command line interface for generating projects and services.
Application generation commands are: # <1>
- create-app NAME
- create-cli-app NAME
- create-function-app NAME
- create-grpc-app NAME
- create-messaging-app NAME
Options:
-h, --help Show this help message and exit.
-v, --verbose Create verbose output.
-V, --version Print version information and exit.
-x, --stacktrace Show full stack trace when exceptions occur.
Commands: # <2>
create-app Creates an application
create-cli-app Creates a CLI application
create-function-app Creates a Cloud Function
create-grpc-app Creates a gRPC application
create-messaging-app Creates a messaging application
create-job Creates a job with scheduled method
create-bean Creates a singleton bean
create-websocket-client Creates a Websocket client
create-client Creates a client interface
create-controller Creates a controller and associated test
feature-diff Produces the diff of an original project with an original project with
additional features.
create-websocket-server Creates a Websocket server
create-test Creates a simple test for the project's testing framework
<1> Here you can see the project generation commands lists
<2> All commands available in the current directory are listed here
<3> Note: the things listed after the project creation commands (always available) depend on the current directory context
All the code-generation commands honor the values written in micronaut-cli.yml. For example, assume the following micronaut-cli.yml file.
.micronaut-cli.yml [source,yaml]
defaultPackage: example
testFramework: spock sourceLanguage: java
With the above settings, the create-bean command (by default) generates a Java class with an associated Spock test, in the example package. Commands accept arguments and these defaults can be overridden on a per-command basis.
== Base Commands
These commands are always available within the context of a micronaut project.
=== Create-Bean
.Create-Bean Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the bean class
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-bean command generates a simple {jakartaapi}/jakarta/inject/Singleton.html[Singleton] class. It does not create an associated test.
[source,bash]
$ mn create-bean EmailService | Rendered template Bean.java to destination src/main/java/example/EmailService.java
=== Create-Job
.Create-Job Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the job class
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-job command generates a simple api:scheduling.annotation.Scheduled[] class. It follows a *Job convention for generating the class name. It does not create an associated test.
[source,bash]
$ mn create-job UpdateFeeds --lang groovy | Rendered template Job.groovy to destination src/main/groovy/example/UpdateFeedsJob.groovy
== HTTP-Related Commands
=== Create-Controller
.Create-Controller Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the controller
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-controller command generates a link:{api}/io/micronaut/http/annotation/Controller.html[Controller] class. It follows a *Controller convention for generating the class name. It creates an associated test that runs the application and instantiates an HTTP client, which can make requests against the controller.
[source,bash]
$ mn create-controller Book | Rendered template Controller.java to destination src/main/java/example/BookController.java | Rendered template ControllerTest.java to destination src/test/java/example/BookControllerTest.java
=== Create-Client
.Create-Client Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the client
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-client command generates a simple api:http.client.annotation.Client[] interface. It follows a *Client convention for generating the class name. It does not create an associated test.
[source,bash]
$ mn create-client Book | Rendered template Client.java to destination src/main/java/example/BookClient.java
=== Create-Websocket-Server
.Create-Websocket-Server Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the server
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-websocket-server command generates a simple api:websocket.annotation.ServerWebSocket[] class. It follows a *Server convention for generating the class name. It does not create an associated test.
[source,bash]
$ mn create-websocket-server MyChat | Rendered template WebsocketServer.java to destination src/main/java/example/MyChatServer.java
=== Create-Websocket-Client
.Create-Websocket-Client Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the client
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-websocket-client command generates a simple api:websocket.WebSocketClient[] abstract class. It follows a *Client convention for generating the class name. It does not create an associated test.
[source,bash]
$ mn create-websocket-client MyChat | Rendered template WebsocketClient.java to destination src/main/java/example/MyChatClient.java
== CLI Project Commands
=== Create-Command
.Create-Command Flags
|===
|Flag|Description|Example
|-l, --lang
|The language used for the command
|--lang groovy
|-f, --force
|Whether to overwrite existing files
|--force
|
|===
The create-command command generates a standalone application that can be executed as a
https://picocli.info[picocli] link:https://picocli.info/apidocs/picocli/CommandLine.Command.html[Command]. It follows a *Command convention for generating the class name. It creates an associated test that runs the application and verifies that a command line option was set.
[source,bash]
$ mn create-command print | Rendered template Command.java to destination src/main/java/example/PrintCommand.java | Rendered template CommandTest.java to destination src/test/java/example/PrintCommandTest.java
This list is just a small subset of the code generation commands in the Micronaut CLI. To see all context-sensitive commands the CLI has available (and under what circumstances they apply), check out the https://github.com/micronaut-projects/micronaut-starter[micronaut-starter] project and find the classes that extend CodeGenCommand. The applies method dictates whether a command is available or not.
Src Main Docs Guide Cli CreateProject (`src/main/docs/guide/cli/createProject.adoc`)
Creating a project is the primary usage of the CLI. The primary command for creating a new project is create-app, which creates a standard server application that communicates over HTTP. For other types of application, see the documentation below.
.Micronaut CLI Project Creation Commands
[cols="1,2,2,4"]
|===
|Command|Description|Options|Example
|create-app
|Creates a basic Micronaut application.
a|
-l,--lang-t,--test-b,--build-f,--features-i,--inplace
a|
[source,bash]
mn create-app my-project --features mongo-reactive,security-jwt --build maven
|create-cli-app
|Creates a command-line Micronaut application.
a|
-l,--lang-t,--test-b,--build-f,--features-i,--inplace
a|
[source,bash]
mn create-cli-app my-project --features http-client,jdbc-hikari --build maven --lang kotlin --test kotest
|create-function-app
|Creates a Micronaut serverless function, using AWS by default.
a|
-l,--lang-t,--test-b,--build-f,--features-i,--inplace
a|
[source,bash]
mn create-function-app my-lambda-function --lang groovy --test spock
|create-messaging-app
|Creates a Micronaut application that only communicates via a messaging protocol. Uses Kafka by default but can be switched to RabbitMQ with --features rabbitmq.
a|
-l,--lang-t,--test-b,--build-f,--features-i,--inplace
a|
[source,bash]
mn create-messaging-app my-broker --lang groovy --test spock
|create-grpc-app
|Creates a Micronaut application that uses gRPC.
a|
-l,--lang-t,--test-b,--build-f,--features-i,--inplace
a|
[source,bash]
mn create-grpc-app my-grpc-app --lang groovy --test spock
|===
== Create Command Flags
The create-* commands generate a basic Micronaut project, with optional flags to specify features, language, test framework, and build tool. All projects except functions include a default Application class for starting the application.
.Flags
[cols="2,3,3"]
|===
|Flag|Description|Example
|-l, --lang
|Language to use for the project (one of java, groovy, kotlin - default is java)
|--lang groovy
|-t, --test
|Test framework to use for the project (one of junit, spock - default is junit)
|--test spock
|-b, --build
|Build tool (one of gradle, gradle_kotlin, maven - default is gradle for the languages java and groovy; default is gradle_kotlin for language kotlin)
|--build maven
|`-f`, `--features` |Features to use for the project, comma-separated a| [source,bash]
--features security-jwt,mongo-reactive
or [source,bash]
-f security-jwt -f mongo-reactive
|-i, --inplace
|If present, generates the project in the current directory (project name is optional if this flag is set)
|--inplace
|===
Once created, the application can be started using the Application class, or the appropriate build tool task.
.Starting a Gradle project [source,bash]
$ ./gradlew run
.Starting a Maven project [source,bash]
$ ./mvnw mn:run
=== Language/Test Features
By default, the create commands generate a Java application, with JUnit configured as the test framework. All the options chosen and features applied are stored as properties in the micronaut-cli.yml file, as shown below:
.micronaut-cli.yml [source,yaml]
applicationType: default defaultPackage: com.example testFramework: junit sourceLanguage: java buildTool: gradle features: [annotation-api, app-name, application, gradle, http-client, java, junit, logback, netty-server, shade, yaml]
Some commands rely on the data in this file to determine if they should be executable. For example, the create-kafka-listener command requires kafka to be one of the features in the list.
NOTE: The values in micronaut-cli.yml are used by the CLI for code generation. After a project is generated, you can edit these values to change the project defaults, however you must supply the required dependencies and/or configuration to use your chosen language/framework. For example, you could change the testFramework property to spock to cause the CLI to generate Spock tests when running commands (such as create-controller), but you need to add the Spock dependency to your build.
==== Groovy
To create an app with Groovy support (which uses Spock by default), supply the appropriate language via the lang flag:
[source,bash]
$ mn create-app my-groovy-app --lang groovy
This includes the Groovy and Spock dependencies in your project, and writes the appropriates values in micronaut-cli.yml.
==== Kotlin
To create an app with Kotlin support (which uses Kotest by default), supply the appropriate language via the lang flag:
[source,bash]
$ mn create-app my-kotlin-app --lang kotlin
This includes the Kotlin and Kotest dependencies in your project, and writes the appropriates values in micronaut-cli.yml.
=== Build Tool
By default, create-app creates a Gradle project, with a build.gradle file in the project root directory. To create an app using the Maven build tool, supply the appropriate option via the build flag:
[source,bash]
$ mn create-app my-maven-app --build maven
== Create-Cli-App
The create-cli-app command generates a <<commandLineApps,Micronaut command line application>> project, with optional flags to specify language, test framework, features, profile, and build tool. By default, the project includes the picocli feature to support command line option parsing. The project will include a *Command class (based on the project name, e.g. hello-world generates HelloWorldCommand), and an associated test which instantiates the command and verifies that it can parse command line options.
Once created, the application can be started using the *Command class, or the appropriate build tool task.
.Starting a Gradle project [source,bash]
$ ./gradlew run
.Starting a Maven project [source,bash]
$ ./mvnw mn:run
== Create Function App
The create-function-app command generates a <<serverlessFunctions,Micronaut function>> project, optimized for serverless environments, with optional flags to specify language, test framework, features, and build tool. The project will include a *Function class (based on the project name, e.g. hello-world generates HelloWorldFunction), and an associated test which instantiates the function and verifies that it can receive requests.
TIP: Currently, AWS Lambda, Micronaut Azure, and Google Cloud are the supported cloud providers for Micronaut functions. To use other providers, add one in the features: --features azure-function or --features google-cloud-function.
== Contribute
The CLI source code is at https://github.com/micronaut-projects/micronaut-starter. Information about how to contribute and other resources are there.
Src Main Docs Guide Cli Features (`src/main/docs/guide/cli/features.adoc`)
Features consist of additional dependencies and configuration to enable specific functionality in your application. Micronaut profiles define a large number of features, including features for many of the configurations provided by Micronaut, such as the <<dataAccess, Data Access Configurations>>
[source,bash]
$ mn create-app my-demo-app --features mongo-reactive
This adds the necessary dependencies and configuration for the https://mongodb.github.io/mongo-java-driver-reactivestreams[MongoDB Reactive Driver] in your application. You can view the available features using the --list-features flag for whichever create command you use.
[source,bash]
$ mn create-app --list-features # Output will be supported features for the create-app command $ mn create-function-app --list-features # Output will be supported features for the create-function-app command, different from above.
Src Main Docs Guide Cli Proxy (`src/main/docs/guide/cli/proxy.adoc`)
To configure the CLI to use an HTTP proxy there are two steps. Configuration options can be passed to the cli through the MN_OPTS environment variable.
For example on *nix systems:
[source,bash]
export MN_OPTS="-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 -Dhttp.proxyUser=test -Dhttp.proxyPassword=test"
The profile dependencies are resolved over HTTPS so the proxy port and host are configured with https., however the user and password are specified with http..
For Windows systems the environment variable can be configured under My Computer/Advanced/Environment Variables.
Src Main Docs Guide Cli Reloading (`src/main/docs/guide/cli/reloading.adoc`)
Reloading (or "hot-loading") refers to the framework reinitializing classes (and parts of the application) when changes to the source files are detected.
Since Micronaut prioritizes startup time and most Micronaut apps can start up within seconds, a productive workflow can often be had by restarting the application as changes are made; for example, by running a test class within an IDE.
However, to have your changes automatically reloaded, Micronaut supports automatic restart and the use of third-party reloading agents.
Src Main Docs Guide Cloud ClientSideLoadBalancing (`src/main/docs/guide/cloud/clientSideLoadBalancing.adoc`)
When <<serviceDiscovery, discovering services>> from Consul, Eureka, or other Service Discovery servers, the api:discovery.DiscoveryClient[] emits a list of available api:discovery.ServiceInstance[].
The Micronaut framework by default automatically performs Round Robin client-side load balancing using the servers in this list. This combined with <<retry, Retry Advice>> adds extra resiliency to your Microservice infrastructure.
The load balancing is handled by the api:http.client.LoadBalancer[] interface, which has a api:http.client.LoadBalancer.select()[] method that returns a Publisher which emits a api:discovery.ServiceInstance[].
The rs:Publisher[] is returned because the process for selecting a api:discovery.ServiceInstance[] may result in a network operation depending on the <<serviceDiscovery, Service Discovery>> strategy employed.
The default implementation of the api:http.client.LoadBalancer[] interface is api:http.client.loadbalance.DiscoveryClientRoundRobinLoadBalancer[]. You can replace this strategy with another implementation to customize how client side load balancing is handled in Micronaut, since there are many different ways to optimize load balancing.
For example, you may wish to load balance between services in a particular zone, or to load balance between servers that have the best overall response time.
To replace the api:http.client.LoadBalancer[], define a bean that <<replaces, replaces>> the api:http.client.loadbalance.DiscoveryClientLoadBalancerFactory[].
In fact that is exactly what the Netflix Ribbon support does, described in the next section.
=== Load Balancer Strategies
Without replacing the load balancer, you can change how it picks among the available instances of a service with the load-balancer-strategy setting of the service. This applies to every @Client of the service id, whether its instances come from micronaut.http.services..urls or from a discovery client:
[configuration]
micronaut:
http:
services:
foo:
urls:
- http://foo1
- http://foo2
load-balancer-strategy: p2c
The built-in strategies are:
|===
|Name |Selection
|round-robin
|Each instance in turn. The default: without the setting, the selection is unchanged.
|random
|A random instance.
|p2c
|Power of two choices: of two random instances, the one with fewer exchanges in flight. An exchange counts from its selection until the client reports its outcome.
|weighted
|Smooth weighted round robin by the weight metadata of the instances. An instance without a valid weight has a weight of 1, and an instance of weight 0 is selected only when every instance has weight 0.
|sticky
|The same instance for the same key, with rendezvous hashing: when an instance goes away only its keys move to another one. Without a key, round robin.
|===
The weight is the weight entry of the api:discovery.ServiceInstance#getMetadata--[metadata] of the instance, as registered with the discovery server, for example the service metadata in Consul or Eureka.
The key of the sticky strategy, such as a session or user id, is the load balancer key of the request: the request attribute named by LoadBalancerKey.ATTRIBUTE, which LoadBalancerKey.set(request, key) sets, see api:http.client.loadbalance.LoadBalancerKey[]. Its string form is hashed, so it should be stable, for example a String or a Long. The request itself is never hashed: a request without a key is load balanced round robin. A load balancer asked directly with api:http.client.LoadBalancer#select-java.lang.Object-[LoadBalancer.select(Object)] uses its argument as the key.
The key can also come from a request header, named with the load-balancer-key-header setting of the service; the attribute takes precedence over the header:
[configuration]
micronaut:
http:
services:
foo:
urls:
- http://foo1
- http://foo2
load-balancer-strategy: sticky
load-balancer-key-header: X-Session-Id
With a strategy configured, an instance whose backup metadata is true is a backup, like the backup servers of nginx: it is selected only when no other instance is available, whatever the strategy. Without the load-balancer-strategy setting, the backup metadata is ignored and a backup instance is selected in turn like any other.
Every strategy picks among the instances that are up and that the <<serviceDiscoveryManual, outlier detection>> has not ejected: an ejected instance is never selected, whatever the strategy. The instances are those of each selection, so instances added or removed by a refresh of the discovery client are taken into account on the next selection.
To pick the instance in another way, implement api:http.client.loadbalance.LoadBalancerStrategy[] and register it as a bean named after the strategy:
snippet::io.micronaut.docs.http.client.loadbalance.PrimaryStrategy[tags="imports,class", indent=0, title="A strategy that prefers the first instance"]
<1> The strategy may keep state per service: a prototype bean gives each service its own instance.
<2> The name of the bean is the value of the load-balancer-strategy setting.
<3> Selects among the available instances; the default report method receives the outcome of each exchange.
[configuration]
micronaut:
http:
services:
foo:
urls:
- http://foo1
- http://foo2
load-balancer-strategy: primary
A bean takes precedence over a built-in strategy of the same name. A strategy applies to the round-robin load balancers that the framework creates; a custom api:http.client.LoadBalancer[] keeps controlling its own selection.
Src Main Docs Guide Cloud CloudConfiguration (`src/main/docs/guide/cloud/cloudConfiguration.adoc`)
Applications built for the Cloud often need to adapt to running in a Cloud environment, read and share configuration in a distributed manner, and externalize configuration to the environment where necessary.
Micronaut's <<environments, Environment>> concept can be configured to be Cloud platform-aware and makes the best effort to detect the underlying active Cloud environment.
To enable this feature you can:
- call
deduceCloudEnvironment(true)on the api:context.ApplicationContextBuilder[] interface when starting Micronaut. For example:
.Enabling Cloud Environment Detection [source,java]
public static void main(String...args) {
Micronaut.build(args)
.deduceCloudEnvironment(true)
.start();
}
- Set the
micronaut.env.cloud-deductionproperty totruein your configuration. - Provide an environment variable
MICRONAUT_ENV_CLOUD_DEDUCTIONset totrue.
You can then use the api:context.annotation.Requires[] annotation to <<conditionalBeans,conditionally load bean definitions>>.
The following table summarizes the constants in the api:context.env.Environment[] interface and provides an example:
.Micronaut Environment Detection
|===
|Constant|Description |Requires Example |Environment name
|api:context.env.Environment#ANDROID[]
|The application is running as an Android application
|@Requires(env = Environment.ANDROID)
|android
|api:context.env.Environment#TEST[]
|The application is running within a JUnit or Spock test
|@Requires(env = Environment.TEST)
|test
|api:context.env.Environment#CLOUD[]
|The application is running in a Cloud environment (present for all other cloud platform types)
|@Requires(env = Environment.CLOUD)
|cloud
|api:context.env.Environment#AMAZON_EC2[]
|Running on https://aws.amazon.com/ec2[Amazon EC2]
|@Requires(env = Environment.AMAZON_EC2)
|ec2
|api:context.env.Environment#GOOGLE_COMPUTE[]
|Running on https://cloud.google.com/compute/[Google Compute]
|@Requires(env = Environment.GOOGLE_COMPUTE)
|gcp
|api:context.env.Environment#KUBERNETES[]
|Running on https://www.kubernetes.io[Kubernetes]
|@Requires(env = Environment.KUBERNETES)
|k8s
|api:context.env.Environment#HEROKU[]
|Running on https://heroku.com[Heroku]
|@Requires(env = Environment.HEROKU)
|heroku
|api:context.env.Environment#CLOUD_FOUNDRY[]
|Running on https://www.cloudfoundry.org[Cloud Foundry]
|@Requires(env = Environment.CLOUD_FOUNDRY)
|pcf
|api:context.env.Environment#AZURE[]
|Running on https://azure.microsoft.com[Microsoft Azure]
|@Requires(env = Environment.AZURE)
|azure
|api:context.env.Environment#IBM[]
|Running on https://www.ibm.com/cloud/[IBM Cloud]
|@Requires(env = Environment.IBM)
|ibm
|api:context.env.Environment#DIGITAL_OCEAN[]
|Running on https://www.digitalocean.com/[Digital Ocean]
|@Requires(env = Environment.DIGITAL_OCEAN)
|digitalocean
|api:context.env.Environment#ORACLE_CLOUD[]
|Running on https://cloud.oracle.com/[Oracle Cloud]
|@Requires(env = Environment.ORACLE_CLOUD)
|oraclecloud
|===
Note that you can have multiple environments active, for example when running in Kubernetes on AWS.
In addition, using the value of the constants defined in the table above you can create environment-specific configuration files. For example if you create a src/main/resources/application-gcp.yml file, it is only loaded when running on Google Compute.
TIP: Any configuration property in the api:context.env.Environment[] can also be set via an environment variable. For example, setting the CONSUL_CLIENT_HOST environment variable overrides the host property in link:{micronautdiscoveryapi}/io/micronaut/discovery/consul/ConsulConfiguration.html[ConsulConfiguration].
== Using Cloud Instance Metadata
When the Micronaut framework detects it is running on a supported cloud platform, on startup it populates the interface api:discovery.cloud.ComputeInstanceMetadata[].
TIP: As of Micronaut framework 2.1.x this logic depends on the presence of the appropriate core Cloud module for Oracle Cloud, AWS, or GCP.
All this data is merged together into the metadata property for the running api:discovery.ServiceInstance[].
To access the metadata for your application instance you can use the interface api:discovery.EmbeddedServerInstance[], and call getMetadata() which returns a Map of the metadata.
If you connect remotely via a client, the instance metadata can be referenced once you have retrieved a api:io.micronaut.discovery.ServiceInstance[] from either the api:http.client.LoadBalancer[] or api:discovery.DiscoveryClient[] APIs.
NOTE: The Netflix Ribbon client-side load balancer can be configured to use the metadata to do zone-aware client-side load balancing. See <<clientSideLoadBalancing,Client-Side Load Balancing>>
To obtain metadata for a service via <<serviceDiscovery,Service Discovery>> use the api:http.client.LoadBalancerResolver[] interface to resolve a api:http.client.LoadBalancer[] and obtain a reference to a service by identifier:
.Obtaining Metadata for a Service instance [source,java]
LoadBalancer loadBalancer = loadBalancerResolver.resolve("some-service");
Flux.from(
loadBalancer.select()
).subscribe((instance) ->
ConvertibleValues
...
);
The api:discovery.EmbeddedServerInstance[] is available through event listeners that listen for the api:discovery.event.ServiceReadyEvent[]. The ann:runtime.event.annotation.EventListener[] annotation makes it easy to listen for the event in your beans.
To obtain metadata for the locally running server, use an <<events,EventListener>> for the api:discovery.event.ServiceReadyEvent[]:
.Obtaining Metadata for a Local Server [source,java]
@EventListener
void onServiceStarted(ServiceReadyEvent event) {
ServiceInstance serviceInstance = event.getSource();
ConvertibleValues
}
Src Main Docs Guide Cloud DistributedTracing (`src/main/docs/guide/cloud/distributedTracing.adoc`)
See the documentation for link:https://micronaut-projects.github.io/micronaut-tracing/latest/guide/[Micronaut Tracing] for more information adding distributed tracing to your applications.
Src Main Docs Guide Cloud ServiceDiscovery (`src/main/docs/guide/cloud/serviceDiscovery.adoc`)
Service Discovery enables Microservices to find each other without knowing the physical location or IP address of associated services.
The Micronaut framework integrates with multiple tools and libraries. See
https://micronaut-projects.github.io/micronaut-discovery-client/latest/guide/[Micronaut Service Discovery documentation]
for more details.
Src Main Docs Guide CommandLineApps Picocli (`src/main/docs/guide/commandLineApps/picocli.adoc`)
https://github.com/remkop/picocli[Picocli] is a command line parser that supports usage help with ANSI colors, autocomplete, and nested subcommands. It has an annotations API to create command line applications with almost no code, and a programmatic API for dynamic uses like creating Domain Specific Languages.
See the https://micronaut-projects.github.io/micronaut-picocli/latest/guide/[documentation for the Picocli integration] for more information.
Src Main Docs Guide Config Banner (`src/main/docs/guide/config/banner.adoc`)
Since Micronaut framework 2.3 a banner is shown when the application starts. It is enabled by default, and it also shows the Micronaut version.
[source,shell,subs="attributes"]
$ ./gradlew run
| / () ___ _ __ ___ _ __ __ _ _ | |
| |/| | |/ | '/ _ | '_ \ / ` | | | | |
| | | | | (| | | () | | | | (| | || | |_
|| |||_|_| _/|| ||_,|_,|__|
Micronaut ({version})
17:07:22.997 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 611ms. Server Running: http://localhost:8080
To customize the banner with your own ASCII Art (just plain ASCII at this moment), create the file src/main/resources/micronaut-banner.txt and it will be used instead.
To disable it, modify your Application class:
[source,java]
public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.banner(false) // <1>
.start();
}}
<1> Disable the banner
Src Main Docs Guide Config Bootstrap (`src/main/docs/guide/config/bootstrap.adoc`)
Most application configuration is stored in your configuration file (e.g application.yml), environment-specific files like application-{environment}.{extension}, environment and system properties, etc.
These configure the application context.
But during application startup, before the application context is created, a "bootstrap" context can be created to store configuration necessary to retrieve additional configuration for the main context. Typically, that additional configuration is in some remote source.
The bootstrap context is enabled depending on the following conditions. The conditions are checked in the following order:
- If The api:context.env.Environment#BOOTSTRAP_CONTEXT_PROPERTY[micronaut.bootstrap.context] system property is set, that value determines if the bootstrap context is enabled.
- If The application context builder option api:context.ApplicationContextBuilder#bootstrapEnvironment[bootstrapEnvironment] is set, that value determines if the bootstrap context is enabled.
- If a api:context.env.BootstrapPropertySourceLocator[] bean is present the bootstrap context is enabled. Normally this comes from the
micronaut-discovery-clientdependency. If you provide a custom api:discovery.config.ConfigurationClient[] for distributed configuration, make sure that dependency is present so the bootstrap/distributed configuration infrastructure is loaded.
Configuration properties that must be present before application context configuration properties are resolved, for example when using distributed configuration, are stored in a bootstrap configuration file. Once it is determined the bootstrap context is enabled (as described above), the bootstrap configuration files are read using the same rules as regular application configuration.
See the <> documentation for the details. The only difference is the prefix (bootstrap instead of application).
The file name prefix bootstrap is configurable with a system property link:{api}/io/micronaut/context/env/Environment.html#BOOTSTRAP_NAME_PROPERTY[micronaut.bootstrap.name].
NOTE: The bootstrap context configuration is carried over to the main context automatically, so it is not necessary for configuration properties to be duplicated in the main context. In addition, the bootstrap context configuration has a higher precedence than the main context, meaning if a configuration property appears in both contexts, then the value will be taken from the bootstrap context first.
That means if a configuration property is needed in both places, it should go into the bootstrap context configuration.
See the <<distributedConfiguration, distributed configuration>> section of the documentation for the list of integrations with common distributed configuration solutions.
=== Bootstrap Context Beans
In order for a bean to be resolvable in the bootstrap context it must be annotated with ann:context.annotation.BootstrapContextCompatible[]. If any given bean is not annotated then it will not be able to be resolved in the bootstrap context. Typically, any bean that is participating in the process of retrieving distributed configuration needs to be annotated.
For example, if you implement custom distributed configuration that is resolved during bootstrap, any supporting beans it depends on must be bootstrap-compatible, and the application must include the discovery client infrastructure, typically by adding the io.micronaut.discovery:micronaut-discovery-client dependency.
Src Main Docs Guide Config ConfigurationProperties (`src/main/docs/guide/config/configurationProperties.adoc`)
You can create type-safe configuration by creating classes that are annotated with
ann:io.micronaut.context.annotation.ConfigurationProperties[].
The Micronaut framework will produce a reflection-free @ConfigurationProperties bean and will also at compile time calculate the property paths to evaluate, greatly improving the speed and efficiency of loading @ConfigurationProperties.
For example:
snippet::io.micronaut.docs.config.properties.EngineConfig[tags="imports,class",indent=0,title="@ConfigurationProperties Example"]
<1> The @ConfigurationProperties annotation takes the configuration prefix
<2> You can use jakarta.validation annotations to validate the configuration
<3> Default values can be assigned to the property
<4> Static inner classes can provide nested configuration
<5> Optional configuration values can be wrapped in java.util.Optional
Once you have prepared a type-safe configuration it can be injected into your beans like any other bean:
snippet::io.micronaut.docs.config.properties.EngineImpl[tags="class",indent=0,title="@ConfigurationProperties Dependency Injection"]
<1> Inject the EngineConfig bean
<2> Use the configuration properties
Configuration values can then be supplied from one of the link:{api}/io/micronaut/context/env/PropertySource.html[PropertySource] instances. For example:
snippet::io.micronaut.docs.config.properties.VehicleSpec[tags="start",indent=0,title="Supply Configuration"]
The above example prints: "Ford Engine Starting V8 [rodLength=6.0]"
You can directly reference configuration properties in @Requires annotation to conditionally load beans using the following syntax: @Requires(bean=Config.class, beanProperty="property", value="true")
Note for more complex configurations you can structure ann:context.annotation.ConfigurationProperties[] beans through inheritance.
For example creating a subclass of EngineConfig with @ConfigurationProperties('bar') will resolve all properties under the path my.engine.bar.
[NOTE]
YAML Reserved Words
When using YAML configuration files, certain words are reserved by the YAML specification and will be automatically converted to boolean values. These include:
* `yes` / `no`
* `true` / `false`
* `on` / `off`
If you use these words as unquoted property keys in YAML, they will be parsed as booleans instead of strings:
[source,yaml]
----
foo:
bar:
yes: "1" # yes is parsed as true (boolean)
no: "2" # no is parsed as false (boolean)
----
To use these words as property names, you must quote the keys:
[source,yaml]
----
foo:
bar:
"yes": "1" # forces YAML to treat as string
"no": "2" # forces YAML to treat as string
----
This is a limitation of YAML/SnakeYAML parsing and not specific to Micronaut. For more information, see the link:https://yaml.org/spec/1.2/spec.html#id2765879[YAML 1.2 Specification].
====
== Includes / Excludes
For the cases where the configuration properties class inherits properties from a parent class, it may be desirable to exclude properties from the parent class. The `includes` and `excludes` members of the ann:io.micronaut.context.annotation.ConfigurationProperties[] annotation allow for that functionality. The list applies to both local properties and inherited properties.
The names supplied to the includes/excludes list must be the "property" name. For example if a setter method is injected, the property name is the de-capitalized setter name (`setConnectionTimeout` -> `connectionTimeout`).
[[configurationPropertiesAccessorsStyle]]
== Change accessors style
Since 3.3, the Micronaut framework supports defining different accessors prefixes for getters and setter other than the default `get` and `set` defined for Java Beans. Annotate your POJO or `@ConfigurationProperties` class with the link:{api}/io/micronaut/core/annotation/AccessorsStyle.html[@AccessorsStyle] annotation.
This is useful when you write the getters and setters in a fluent way. For example:
.Using `@AccessorsStyle`
[source,java]
----
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.AccessorsStyle;
@AccessorsStyle(readPrefixes = "", writePrefixes = "") // <1>
@ConfigurationProperties("my.engine")
public class EngineConfig {
private String manufacturer;
private int cylinders;
public EngineConfig(String manufacturer, int cylinders) {
this.manufacturer = manufacturer;
this.cylinders = cylinders;
}
public String manufacturer() { // <2>
return manufacturer;
}
public void manufacturer(String manufacturer) { // <2>
this.manufacturer = manufacturer;
}
public int cylinders() { // <2>
return cylinders;
}
public void cylinders(int cylinders) { // <2>
this.cylinders = cylinders;
}
}
----
<1> The Micronaut framework will use an empty prefix for getters and setters.
<2> Define the getters and setters with an empty prefix.
Now you can inject `EngineConfig` and use it with `engineConfig.manufacturer()` and `engineConfig.cylinders()` to retrieve the values from configuration.
== Property Type Conversion
The Micronaut framework uses the api:io.micronaut.core.convert.ConversionService[] bean to convert values when resolving properties. You can register additional converters for types not supported by Micronaut by defining beans that implement the api:io.micronaut.core.convert.TypeConverter[] interface.
The Micronaut framework features some built-in conversions that are useful, which are detailed below.
=== Duration Conversion
Durations can be specified by appending the unit with a number. Supported units are `s`, `ms`, `m` etc. The following table summarizes examples:
.Duration Conversion
|===
|Configuration Value |Resulting Value
|`10ms` | `Duration` of 10 milliseconds
|`10m` | `Duration` of 10 minutes
|`10s` | `Duration` of 10 seconds
|`10d` | `Duration` of 10 days
|`10h` | `Duration` of 10 hours
|`10ns` | `Duration` of 10 nanoseconds
|`PT15M` | `Duration` of 15 minutes using ISO-8601 format
|===
For example to configure the default HTTP client read timeout:
[configuration,title="Using Duration Values"]
----
micronaut:
http:
client:
read-timeout: 15s
----
=== List / Array Conversion
Lists and arrays can be specified in Java properties files as comma-separated values, or in YAML using native YAML lists. The generic types are used to convert the values. For example in YAML:
[configuration,title="Specifying lists or arrays in YAML"]
----
my:
app:
integers:
- 1
- 2
urls:
- http://foo.com
- http://bar.com
----
For the above example configurations you can define properties to bind to with the target type supplied via generics:
[source,java]
List integers;
List urls;
=== Readable Bytes
You can annotate any setter parameter with ann:io.micronaut.core.convert.format.ReadableBytes[] to allow the value to be set using a shorthand syntax for specifying bytes, kilobytes etc. For example the following is taken from api:io.micronaut.http.client.HttpClientConfiguration[]:
.Using `@ReadableBytes`
[source,java]
----
public void setMaxContentLength(@ReadableBytes int maxContentLength) {
this.maxContentLength = maxContentLength;
}
----
With the above in place you can set `micronaut.http.client.max-content-length` using the following values:
.@ReadableBytes Conversion
|===
|Configuration Value |Resulting Value
|`10mb` | 10 megabytes
|`10kb` | 10 kilobytes
|`10gb` | 10 gigabytes
|`1024` | A raw byte length
|===
=== Formatting Dates
The ann:io.micronaut.core.convert.format.Format[] annotation can be used on setters to specify the date format to use when binding `java.time` date objects.
.Using `@Format` for Dates
[source,java]
----
public void setMyDate(@Format("yyyy-MM-dd") LocalDate date) {
this.myDate = date;
}
----
== Configuration Builder
Many frameworks and tools already use builder-style classes to construct configuration.
You can use the link:{api}/io/micronaut/context/annotation/ConfigurationBuilder.html[@ConfigurationBuilder] annotation to populate a builder-style class with configuration values. link:{api}/io/micronaut/context/annotation/ConfigurationBuilder.html[ConfigurationBuilder] can be applied to fields or methods in a class annotated with link:{api}/io/micronaut/context/annotation/ConfigurationProperties.html[@ConfigurationProperties].
Since there is no consistent way to define builders in the Java world, one or more method prefixes can be specified in the annotation to support builder methods like `withXxx` or `setXxx`. If the builder methods have no prefix, assign an empty string to the parameter.
A configuration prefix can also be specified to tell the Micronaut framework where to look for configuration values. By default, builder methods use the configuration prefix specified in a class-level link:{api}/io/micronaut/context/annotation/ConfigurationProperties.html[@ConfigurationProperties] annotation.
For example:
.@ConfigurationBuilder Example
snippet::io.micronaut.docs.config.builder.EngineConfig[tags="imports,class"]
<1> The `@ConfigurationProperties` annotation takes the configuration prefix
<2> The first builder can be configured without the class configuration prefix; it inherits from the above.
<3> The second builder can be configured with the class configuration prefix + the `configurationPrefix` value.
<4> The third builder demonstrates that the annotation can be applied to a method as well as a property.
NOTE: By default, only single-argument builder methods are supported. For methods with no arguments, set the `allowZeroArgs` parameter of the annotation to `true`.
Like in the previous example, we can construct an `EngineImpl`. Since we are using a builder, we can use a factory class to build the engine from the builder.
.Factory Bean
snippet::io.micronaut.docs.config.builder.EngineFactory[tags="imports,class"]
The engine that was returned can then be injected anywhere an engine is required.
Configuration values can be supplied from one of the link:{api}/io/micronaut/context/env/PropertySource.html[PropertySource] instances. For example:
.Supply Configuration
snippet::io.micronaut.docs.config.builder.VehicleSpec[tags="start"]
The above example prints: `"Subaru Engine Starting V4 [rodLength=4.0, sparkPlug=Iridium(NGK 6619 LFR6AIX)]"`
== MapFormat
For some use cases it may be desirable to accept a map of arbitrary configuration properties that can be supplied to a bean, especially if the bean represents a third-party API where not all the possible configuration properties are known. For example, a datasource may accept a map of configuration properties specific to a particular database driver, allowing the user to specify any desired options in the map without coding each property explicitly.
For this purpose, the api:core.convert.format.MapFormat[] annotation lets you bind a map to a single configuration property, and specify whether to accept a flat map of keys to values, or a nested map (where the values may be additional maps).
snippet::io.micronaut.docs.config.mapFormat.EngineConfig[tags="imports,class",indent=0,title="@MapFormat Example"]
<1> Note the `transformation` argument to the annotation; possible values are `MapTransformation.FLAT` (for flat maps) and `MapTransformation.NESTED` (for nested maps)
snippet::io.micronaut.docs.config.mapFormat.EngineImpl[tags="class",indent=0,title="EngineImpl"]
Now a map of properties can be supplied to the `my.engine.sensors` configuration property.
snippet::io.micronaut.docs.config.mapFormat.VehicleSpec[tags="start",indent=0,title="Use Map Configuration"]
The above example prints: `"Engine Starting V8 [sensors=2]"`
== Configuration Metadata
At compilation time, the Micronaut framework writes metadata about every `@ConfigurationProperties` and `@EachProperty` type, which tooling uses to complete, document, and validate configuration:
* `META-INF/spring-configuration-metadata.json` lists the groups and properties of the module, with their types, descriptions, and default values.
* `META-INF/micronaut-configuration-schemas/<class>.json` is a https://json-schema.org/draft/2020-12/schema[JSON Schema (Draft 2020-12)] of each configuration class. The configuration validator of https://micronaut-projects.github.io/micronaut-json-schema/latest/guide/[Micronaut JSON Schema] uses these schemas to validate the configuration of an application.
The description of a property comes from its Javadoc, and its default value from ann:core.bind.annotation.Bindable[] `defaultValue` or, for the JSON schema, a `DEFAULT_` constant of the class, such as `DEFAULT_AUTO_OFFSET_RESET`. The JSON schema of a property also reflects its type and annotations:
|===
|Java type or annotation |JSON schema
|`boolean`, integer, and decimal types
|`type` `boolean`, `integer`, or `number`
|An enum
|`type` `string` and `enum` with the constants of the enum
|`URI` or `URL`, and `Duration`
|`type` `string` and `format` `uri` or `duration`
|A collection or an array, and a `Map`
|`type` `array` with the schema of the elements as `items`, and `type` `object` with the schema of the values as `additionalProperties`
|`@NotNull` and `@NotBlank`
|The property is `required`
|`@Size`, `@NotEmpty`, `@NotBlank`
|`minLength` and `maxLength`, `minItems` and `maxItems`, or `minProperties` and `maxProperties`
|`@Min`, `@Max`, `@DecimalMin`, `@DecimalMax`, `@Positive`, `@PositiveOrZero`, `@Negative`, `@NegativeOrZero`
|`minimum`, `maximum`, `exclusiveMinimum`, and `exclusiveMaximum`
|`@Pattern`, `@Digits`, and `@Email`
|`pattern`, and `format` `email`
|`@AssertTrue` and `@AssertFalse`
|`const` `true` or `false`
|ann:core.annotation.AllowedValues[]
|`enum` with the values of the annotation
|`@Deprecated`
|`deprecated` `true`
|===
=== Allowed Values
When a property only accepts some values, but its type does not list them, such as a `String` option of a third-party library, annotate the field, the setter, or the record component with ann:core.annotation.AllowedValues[]. The values become the `enum` of the property in its JSON schema, so the configuration validator rejects any other value:
.@AllowedValues Example
[source,java]
----
@ConfigurationProperties("my.consumer")
public class ConsumerConfiguration {
@AllowedValues({"earliest", "latest", "none"}) // <1>
private String autoOffsetReset = "latest";
@AllowedValues({"1", "2", "4"}) // <2>
private int threads = 1;
@AllowedValues({"gzip", "snappy", "zstd"}) // <3>
private List<String> codecs = List.of();
// getters and setters
}
----
<1> The schema of `my.consumer.auto-offset-reset` is `{"type":"string","enum":["earliest","latest","none"]}`
<2> The values are written with the JSON type of the property, here `[1,2,4]`. A value that does not convert to the type of the property, or for an enum property is not one of its constants, fails the compilation
<3> For a collection or an array, the values apply to its elements, and for a `Map` to its values
On an enum property, ann:core.annotation.AllowedValues[] narrows the constants that the configuration accepts. You can also annotate your own annotation with ann:core.annotation.AllowedValues[] to reuse a set of values across properties.
NOTE: ann:core.annotation.AllowedValues[] is metadata for tooling: binding the property does not check its value. To validate the value at runtime, also use a constraint such as `@Pattern` with <>.
TIP: See the guide for https://guides.micronaut.io/latest/micronaut-configuration.html[@Configuration and @ConfigurationBuilder] to learn more.
---
### Src Main Docs Guide Config CustomTypeConverter (`src/main/docs/guide/config/customTypeConverter.adoc`)
The Micronaut framework includes an extensible type conversion mechanism. To add additional type converters you register beans of type api:core.convert.TypeConverter[].
The following example shows how to use one of the built-in converters (Map to an Object) or create your own.
Consider the following api:context.annotation.ConfigurationProperties[]:
snippet::io.micronaut.docs.config.converters.MyConfigurationProperties[tags="class", indent=0]
The type `MyConfigurationProperties` has a property named `updatedAt` of type link:{jdkapi}/java.base/java/time/LocalDate.html[LocalDate].
To bind this property from a map via configuration:
snippet::io.micronaut.docs.config.converters.MyConfigurationPropertiesSpec[tags="runContext", indent=0]
<1> Note how we match the `myapp` prefix and `updatedAt` property name in our `MyConfigurationProperties` class above
This won't work by default, since there is no built-in conversion from `Map` to `LocalDate`. To resolve this, define a custom api:core.convert.TypeConverter[]:
snippet::io.micronaut.docs.config.converters.MapToLocalDateConverter[tags="imports,class", indent=0]
<1> The class implements api:core.convert.TypeConverter[] which has two generic arguments, the type you are converting from, and the type you are converting to
<2> The constructor injects a bean of type `ConversionService`, introduced in Micronaut 4, instead of making static calls to `ConversionService.SHARED` used in previous versions
<3> The implementation delegates to the injected conversion service to convert the values from the Map used to create a `LocalDate`
<4> If an exception occurs during binding, call `reject(..)` which propagates additional information to the container
NOTE: It's possible to add a custom type converter into `ConversionService.SHARED` by registering it in a api:core.convert.TypeConverterRegistrar[] via the service loader.
---
### Src Main Docs Guide Config EachBean (`src/main/docs/guide/config/eachBean.adoc`)
The link:{api}/io/micronaut/context/annotation/EachProperty.html[@EachProperty] annotation is a great way to drive dynamic configuration, but typically you want to inject that configuration into another bean that depends on it. Injecting a single instance with a hard-coded qualifier is not a great solution, hence `@EachProperty` is typically used in combination with link:{api}/io/micronaut/context/annotation/EachBean.html[@EachBean]:
snippet::io.micronaut.docs.config.env.DataSourceFactory[tags="eachBean", indent=0, title="Using @EachBean"]
<1> The above example defines a bean link:{api}/io/micronaut/context/annotation/Factory.html[Factory] that creates instances of `javax.sql.DataSource`.
<2> The link:{api}/io/micronaut/context/annotation/EachBean.html[@EachBean] annotation indicates that a new `DataSource` bean will be created for each `DataSourceConfiguration` defined in the previous section.
<3> The `DataSourceConfiguration` instance is injected as a method argument and used to drive the configuration of each `javax.sql.DataSource`
NOTE: `@EachBean` requires that the parent bean has a `@Named` qualifier, since the qualifier is inherited by each bean created by `@EachBean`.
In other words, to retrieve the `DataSource` created by `test.datasource.one` you can do:
snippet::io.micronaut.docs.config.env.EachBeanTest[tags="beans", indent=0, title="Using a Qualifier"]
<1> We demonstrate here that there are indeed two data sources. How can we get one in particular?
<2> By using `Qualifiers.byName("one")`, we can select which of the two beans we'd like to reference.
---
### Src Main Docs Guide Config EachProperty (`src/main/docs/guide/config/eachProperty.adoc`)
The link:{api}/io/micronaut/context/annotation/ConfigurationProperties.html[@ConfigurationProperties] annotation is great for a single configuration class, but sometimes you want multiple instances, each with its own distinct configuration. That is where link:{api}/io/micronaut/context/annotation/EachProperty.html[EachProperty] comes in.
The ann:context.annotation.EachProperty[] annotation creates a `ConfigurationProperties` bean for each sub-property within the given name. As an example consider the following class:
snippet::io.micronaut.docs.config.env.DataSourceConfiguration[tags="eachProperty", indent=0, title="Using @EachProperty"]
<1> The `@EachProperty` annotation defines the property name to be handled.
<2> The `@Parameter` annotation can be used to inject the name of the sub-property that defines the name of the bean (which is also the bean qualifier)
<3> Each property of the bean is bound to configuration.
NOTE: Micronaut configuration uses kebap case, not lower camel case. For example, using `@EachProperty("my-bean")` works, but `@EachProperty("myBean")` fails.
The above `DataSourceConfiguration` defines a `url` property to configure one or more data sources. The URLs themselves can be configured using any of the link:{api}/io/micronaut/context/env/PropertySource.html[PropertySource] instances evaluated to Micronaut:
snippet::io.micronaut.docs.config.env.EachPropertyTest[tags="config", indent=0, title="Providing Configuration to @EachProperty"]
In the above example two data sources (called `one` and `two`) are defined under the `test.datasource` prefix defined earlier in the `@EachProperty` annotation. Each of these configuration entries triggers the creation of a new `DataSourceConfiguration` bean such that the following test succeeds:
snippet::io.micronaut.docs.config.env.EachPropertyTest[tags="beans", indent=0, title="Evaluating Beans Built by @EachProperty"]
<1> All beans of type `DataSourceConfiguration` can be retrieved using `getBeansOfType`
<2> Individual beans can be retrieved by using the `byName` qualifier.
=== List-Based Binding
The default behavior of ann:context.annotation.EachProperty[] is to bind from a map style of configuration, where the key is the named qualifier of the bean and the value is the data to bind from. For cases where map style configuration doesn't make sense, it is possible to inform the Micronaut framework that the class is bound from a list. Simply set the `list` member on the annotation to true.
snippet::io.micronaut.docs.config.env.RateLimitsConfiguration[tags="clazz", indent=0, title="@EachProperty List Example"]
<1> The `list` member of the annotation is set to `true`
<2> Implement `Ordered` if order matters when retrieving the beans
<3> The index is injected into the constructor
---
### Src Main Docs Guide Config Environments (`src/main/docs/guide/config/environments.adoc`)
The application environment is modelled by the api:context.env.Environment[] interface, which allows specifying one or many unique environment names when creating an api:context.ApplicationContext[].
snippet::io.micronaut.docs.context.env.EnvironmentSpec[tags="env",indent=0,title="Initializing the Environment"]
The active environment names allow loading different configuration files depending on the environment, and also using the ann:context.annotation.Requires[] annotation to conditionally load beans or bean ann:context.annotation.Configuration[] packages.
In addition, the Micronaut framework attempts to detect the current environments. For example within a Spock or JUnit test the api:context.env.Environment#TEST[] environment is automatically active.
Additional active environments can be specified using the `micronaut.environments` system property or the `MICRONAUT_ENVIRONMENTS` environment variable. These are specified as a comma-separated list. For example:
.Specifying environments
[source,bash]
----
$ java -Dmicronaut.environments=foo,bar -jar myapp.jar
----
The above activates environments called `foo` and `bar`.
It is also possible to enable the detection of the Cloud environment the application is deployed to (this feature is disabled by default since Micronaut framework 4). See the section on <<cloudConfiguration,Cloud Configuration>> for more information.
---
### Src Main Docs Guide Config EvaluatedExpressions (`src/main/docs/guide/config/evaluatedExpressions.adoc`)
Since 4.0, Micronaut framework supports embedding evaluated expressions in annotation values using `#{...}` syntax which
allows to achieve even more flexibility while configuring your application.
.Evaluated Expression example
[source,groovy]
----
@Value("#{ T(Math).random() }")
double injectedValue;
----
Expressions can be defined whenever an annotation member accepts a string or an array of strings.
NOTE: Expressions are currently not supported for "type use" annotations (that declare `ElementType.TYPE_USE`).
.Evaluated Expression in array
[source,java]
----
@Singleton
@Requires(env = {"dev", "#{ 'test' }"})
public class EvaluatedExpressionInArray {}
----
You can also embed one or more expressions in a string template in a similar manner to embedding properties with the `${...}` syntax.
.Evaluated Expression template
[source,groovy]
----
@Value("http://#{'hostname'}/#{'path'}")
String url;
----
Evaluated Expressions are validated and compiled at build time which guarantees type safety at runtime.
Once an application is running expressions are evaluated on demand as part of annotation metadata resolution. The
usage of expressions does not impact performance as evaluation process is completely reflection free.
Note that, for security reasons expressions cannot be dynamically compiled at runtime from potentially untrusted
input. All expressions are compiled and checked statically during the compilation process of the application with
errors reported as compilation failures.
In general, expressions can be treated as statement written using a programming language with reduced
set of available features. Even though the complexity of expression is only limited by the list of supported syntax
constructs, it is in general not recommended to place complex logic inside an expression as there are usually better
ways to achieve the same result.
== Using Expressions in Micronaut framework
Expressions can be used anywhere throughout the Micronaut framework and associated modules, but as an example, you can use them to implement simple scheduled job control, for example:
snippet::io.micronaut.docs.expressions.ExampleJob[tags="imports,clazz",title="Job Control with Expressions"]
<1> Here the `condition` member of the ann:scheduling.annotation.Scheduled[] annotation is used to only execute the job if a pre-condition is met.
<2> The `condition` invokes a method of the type that checks if the job is paused. Other methods can be used to pause and resume execution of the job as desired.
TIP: You can also use expressions to perform conditional routing using the ann:http.annotation.RouteCondition[] annotation.
== Evaluated Expression Language Reference
The Evaluated Expressions syntax supports the following functionality:
* Literal Values
* Math Operators
* Comparison Operators
* Logical Operators
* Ternary Operator
* Type References
* Method Invocation
* Property Access
* Retrieving Beans from Bean Context
* Retrieving Environment Properties
=== Literal Values
The following types of literal values are supported:
* `null`
* boolean values (`true`, `false`)
* strings, which need to be surrounded with single quotation mark (`'`)
* numeric values (`int`, `long`, `float`, `double`)
Integer and Long values can also be specified in hexadecimal or octal notation. Float and Double values can also be
specified in exponential notation. All numeric values can be negative as well.
.Literal values examples
[source]
----
#{ null }
#{ true }
#{ 'string value' }
#{ 10 }
#{ 0xFFL }
#{ 10L }
#{ .123f }
#{ 1E+1d }
#{ 123D }
----
=== Math Operators
The supported mathematical operators are `+`, `-`, `*`, `/`, `%`, `^`. Math operators can only be applied to numeric
values (except `+` which can be used for string concatenation as well). Mathematical operations are performed in order
enforced by standard operator precedence. You can also change evaluation order by using brackets `()`.
`/` and `%` operators can be aliased by `div` and `mod` keywords respectively.
.Math operators examples
[source]
----
#{ 1 + 2 } // 3
#{ 'a' + 'b' + 'c' } // 'abc'
#{ 7 - 3 } // 4
#{ 7 * 3 } // 21
#{ 7 * ( 3 + 1) } // 28
#{ 15 / 3 } // 5
#{ 15 div 3 } // 5
#{ 15 % 3 } // 0
#{ 15 mod 3 } // 0
// Unlike in Java, ^ operator means exponentiation
#{ 3 ^ 2 } // 9
----
=== Comparison Operators
The following comparison operators are supported: `==`, `!=`, `>`, `<`, `>=`, `\<=`, `matches`
Comparison operations are performed in order enforced by standard operator precedence.
You can also change evaluation order by using brackets `()`.
Equality check is supported for both primitive types and objects. It is performed using `Object.equals()` method.
`>`, `<`, `>=`, `\<=` operations can be applied to numeric types or types that implement `java.lang.Comparable`
interface.
`matches` keyword can be used to determine whether a string matches provided regular expression which has to
be specified as string literal. The regular expression itself will be checked for validity at compilation time.
.Comparison operators examples
[source]
----
#{ 1 + 2 == 3 } // true
#{ 'abc' != 'abc' } // false
#{ 7 > 3 } // true
#{ 7 < 3 } // false
#{ 7 >= 7 } // true
#{ 7 <= 8 } // false
#{ 'AbC' matches '[A-Za-z*' } // Compilation failure
#{ 'AbC' matches '[A-Za-z]*' } // true
#{ 'AbC' matches '[a-z]*' } // false
----
=== Logical Operators
The following logical operators are supported:
* `&&` (can be aliased with `and`)
* `||` (can be aliased with `or`),
* `!` (can be aliaded with `not`)
* `empty` / `not empty` (works with strings, collections, arrays, and maps)
Logical operations are performed in order enforced by standard operator precedence.
You can also change evaluation order by using brackets `()`.
.Logical operators examples
[source]
----
#{ true && false } // false
#{ true and true } // true
#{ true || false } // true
#{ false or false } // false
#{ !false } // true
#{ !!true } // true
#{ empty '' } // true
#{ not empty '' } // false
----
=== Ternary Operator
A standard ternary operator is supported to allow specifying if-then-else conditional logic in expression
[source]
----
condition ? thenBranch : elseBranch
----
where `condition` evaluation should provide boolean value, and the complexity of `then` and `else` branches is not
limited.
.Ternary operator examples
[source]
----
#{ 15 > 10 ? 'a' : 'b' } // 'a'
#{ 15 >= 16 ? 'a' : 'b' } // 'b'
----
=== Dot and Safe Navigation Operator
The dot operator can be used to access methods and properties of a value within an expression. For example:
.Dot operator usage
[source]
----
#{ collection.size() > 0 }
#{ foo.bar.name == "Fred" }
----
You can also use the safe dereference operator `?.` to navigate paths in a null safe way:
.Safe dereference operator
[source]
----
#{ foo?.bar?.name == "Fred" }
----
TIP: When used, the safe dereference operator will also automatically unwrap Java's `Optional` type.
=== Type References
A predefined syntax construct `T(...)` can be used to reference a class. The value inside brackets should be fully
qualified class name (including the package name). The only exception is `java.lang.*` classes which can be referenced
directly by only specifying the simple class name. Primitive types can not be referenced.
Type References are evaluated in different ways depending on the context.
==== Simple type reference
A simple type reference is resolved as a `Class<?>` object.
.Type reference example
[source]
----
#{ T(java.lang.String) } // String.class
----
Same rule applies if type reference is specified as a method argument.
==== Type check with `instanceof`
A Type Reference can be used as the right-hand side part of the `instanceof` operator
.Type check example
[source]
----
#{ 'abc' instanceof T(String) } // true
----
which is equivalent to the following Java code and will be evaluated as a boolean value:
[source]
----
"abc" instanceof String
----
==== Static method invocation
Type Reference can be used to invoke a static method of a class
.Static method invocation
[source]
----
#{ T(Math).random() }
----
=== Expression Evaluation Context
By default, the only methods you can invoke inside Evaluated Expressions are static methods using type references.
The available methods can be extended by extended the evaluation context. There are two ways to extend the evaluation context. The first involves registering new context class via a custom api:TypeElementVisitor[].
NOTE: The api:TypeElementVisitor[] has to be on the annotation processor classpath, therefore needs to be defined in a separate module that can be included on this classpath.
Once a class is registered within evaluation context the methods and properties of the class are available for referencing in evaluated expressions.
Consider the following example:
[source, java]
.User-defined evaluated expression context
----
include::test-suite/src/main/java/io/micronaut/docs/expressions/CustomEvaluationContext.java[tag=clazz]
----
NOTE: The class should be resolvable as a bean can use `jakarta.inject` annotations to inject other types if necessary. In addition, for performance reasons all evaluation context classes are effectively singleton regardless of the defined scope.
Registering this class can be achieved with a custom implementation of api:expressions.context.ExpressionEvaluationContextRegistrar[] that is registered via service loader as a api:inject.visitor.TypeElementVisitor[] (create a new `META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor` file referencing the new class) and placed on the annotation processor classpath:
[source, java]
.Defining a ExpressionEvaluationContextRegistrar
----
include::test-suite/src/main/java/io/micronaut/docs/expressions/ContextRegistrar.java[tag=clazz]
----
Method `generateRandom(int, int)` can now be used within Evaluated Expression in the following way:
snippet::io.micronaut.docs.expressions.ContextConsumer[title="Usage of user-defined evaluated expression context"]
At runtime, the bean will be retrieved from
application context and respective method will be invoked.
If a matching method is not found within evaluation context at compilation time, the compilation will fail. A
compilation error will also occur if multiple suitable methods are found in the evaluation context, keep that in mind
if you provide multiple api:expressions.context.ExpressionEvaluationContextRegistrar[] that a conflict can occur as these types are effectively global.
The methods will be considered ambiguous (leading to compilation failure) when their names are the same and list of
provided arguments matches multiple methods parameters.
Using a api:expressions.context.ExpressionEvaluationContextRegistrar[] makes its methods and properties available for evaluated
expressions within any annotation in a global manner.
However, you can also specify evaluation context scoped to concrete annotation or
annotation member using ann:context.annotation.AnnotationExpressionContext[].
snippet::io.micronaut.docs.expressions.AnnotationContextExample[title="Usage of annotation level evaluated expression context"]
<1> Here two new methods are introduced to the context called `firstValue()` and `secondValue()` only for the scope of the `@CustomAnnotation`
<2> The `firstValue()` method is defined in a bean called `AnnotationContext`
<3> The `secondValue()` method is defined in a bean called `AnnotationMemberContext`
<4> On the `@CustomAnnotation` annotation the methods of the `AnnotationContext` type are exposed to all members of the annotation (type level context).
<5> On the `value()` member of the `@CustomAnnotation` annotation the methods of the `AnnotationMemberContext` type are made available but scoped only to the `value()` member.
Again context classes need to be explicitly defined as beans to make them available for retrieval from
application context at runtime.
When the annotation is placed on a method, the parameters of the method can also be referenced by name. A parameter with the same name as a property of the context class makes the reference ambiguous and compilation fails. If the expressions of an annotation are evaluated without the method arguments, set `methodArguments = false` on ann:context.annotation.AnnotationExpressionContext[] to leave the method parameters out of its evaluation context. ann:http.annotation.RouteCondition[] does this, so `request` always refers to the request being routed, even on a method with a parameter named `request`.
=== Method Invocation
You can invoke both static methods using type references, methods from evaluation context and methods on objects,
which means method chaining is supported.
.Chaining methods in expression
[source,java]
----
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
class CustomEvaluationContext {
public String stringValue() {
return "stringValue";
}
}
@Singleton
class ContextConsumer {
@Value("#{ #stringValue().length() }")
public int stringLength;
}
----
Varargs methods invocation is supported as well. Note that if last parameter of a method is an array, you can still
invoke it providing list of arguments separated by comma without explicitly wrapping it into array. So in this case
it will be treated in same way as if last method argument was explicitly specified as varargs parameter.
.Invoking varargs methods in expressions
[source,java]
----
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
class CustomEvaluationContext {
public int countIntegers(int... values) {
return values.length;
}
public int countStrings(String[] values) {
return values.length;
}
}
@Singleton
class ContextConsumer {
@Value("#{ #countIntegers(1, 2, 3) }")
public int totalIntegers;
@Value("#{ #countStrings('a', 'b', 'c') }")
public int totalStrings;
}
----
=== Property Access
JavaBean properties can be accessed simply be referencing their names from evaluation context prefixed with `#`. Bean
properties can also be chained with dot in the same way as methods.
.Accessing bean properties in expressions
[source,java]
----
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
class CustomEvaluationContext {
public String getName() {
return "Bob";
}
public int getAge() {
return 25;
}
}
@Singleton
class ContextConsumer {
@Value("#{ 'Name is ' + #name + ', age is ' + #age }")
public String value;
}
----
=== Retrieving Beans from Bean Context
A predefined syntax construct `ctx[...]` can be used to retrieve beans from bean
context. The argument inside square brackets has to be a fully qualified class name (note that `T(...)` wrapper is
optional and can be omitted for simplicity).
.Retrieving beans from bean context
[source]
----
#{ ctx[T(io.micronaut.example.ContextBean)] }
#{ ctx[io.micronaut.example.ContextBean] }
----
=== Retrieving Environment Properties
A syntax construct `env[...]` can be used to retrieve environment properties by name.
The expression inside square brackets has to resolve to string value, otherwise compilation will fail. If property
value will be absent at runtime, the expression will return `null`
.Retrieving Environment Properties
[source]
----
#{ env['test.property'] }
----
---
### Src Main Docs Guide Config ImmutableConfig (`src/main/docs/guide/config/immutableConfig.adoc`)
Since 1.3, Micronaut framework supports the definition of immutable configuration. Immutable configuration with an interface requires the Micronaut Context dependency.
dependency::micronaut-context[]
`micronaut-context` is a transitive dependency of `micronaut-http`. If you use a Micronaut HTTP runtime, your project already includes the `micronaut-context` dependency.
Micronaut framework offers several styles of immutable configuration. They bind values the same way but differ in how a bean behaves when the configuration is <<refreshable, refreshed>> at runtime.
== Interface-based configuration
One approach is to declare an interface annotated with ann:context.annotation.ConfigurationProperties[]. It reflects refreshed configuration automatically, without any additional annotation:
snippet::io.micronaut.docs.config.itfce.EngineConfig[tags="imports,class",indent=0,title="@ConfigurationProperties Interface"]
<1> The ann:context.annotation.ConfigurationProperties[] annotation takes the configuration prefix and is declared on an interface
<2> You can use ann:core.bind.annotation.Bindable[] to set a default value
<3> Validation annotations can also be used
<4> You can reference other ann:context.annotation.ConfigurationProperties[] beans
<5> You can nest immutable configuration
<6> Optional configuration is indicated by returning an `Optional` or specifying `@Nullable`
Micronaut framework provides a compile-time implementation whose getters delegate to the `getProperty(..)` method of the api:context.env.Environment[] interface. Because each getter reads the `Environment` on every call, an interface bean always reflects the current configuration, including after a <<refreshable, refresh>> β even when it was injected into a long-lived bean before the refresh happened.
NOTE: Only getter methods are allowed (default methods are supported). Declaring any other abstract method causes a compilation error.
== Constructor-based configuration
Alternatively, define a class and annotate one of its constructors with ann:context.annotation.ConfigurationInject[] on a ann:context.annotation.ConfigurationProperties[] or ann:context.annotation.EachProperty[] bean:
snippet::io.micronaut.docs.config.immutable.EngineConfig[tags="imports,class",indent=0,title="@ConfigurationInject Constructor"]
<1> The ann:context.annotation.ConfigurationProperties[] annotation takes the configuration prefix
<2> The ann:context.annotation.ConfigurationInject[] annotation is declared on the constructor
<3> You can use ann:core.bind.annotation.Bindable[] to set a default value
<4> Validation annotations can be used too
<5> You can nest immutable configuration
<6> Optional configuration is indicated with `@Nullable`
The ann:context.annotation.ConfigurationInject[] annotation tells the framework to prioritize binding values from configuration over injecting beans. Binding is skipped for a constructor parameter that meets any of these conditions:
* The parameter is annotated with `@Value` (explicit binding)
* The parameter is annotated with `@Property` (explicit binding)
* The parameter is annotated with `@Parameter` (parameterized bean handling)
* The parameter is annotated with `@Inject` (generic bean injection)
* The type of the parameter is annotated with a bean scope (such as `@Singleton`)
Unlike the interface approach, a constructor-based bean binds its values once, when it is constructed, and stores them in fields. Adding the ann:runtime.context.scope.Refreshable[] annotation to the class puts it in the refresh scope, so that a <<refreshable,runtime configuration refresh event>> discards the instance and the next lookup or injection builds a new one from the current configuration.
WARNING: ann:runtime.context.scope.Refreshable[] does not update a reference that has already been injected. A bean that received the configuration in its constructor β a singleton, for example β keeps the instance it was given, with the values it held at that time. Use interface-based configuration, or look the configuration bean up from the api:context.BeanContext[] when you need it, if a long-lived bean must observe a refresh.
== Record classes
It is also possible to use a record class -- a https://docs.oracle.com/en/java/javase/17/language/records.html[Java record], a Kotlin data class or a frozen Python dataclass -- for immutable configuration with ann:context.annotation.ConfigurationProperties[]. The canonical (record) or primary (data class) constructor is treated as though it were annotated with ann:context.annotation.ConfigurationInject[], so no annotation is needed on the constructor:
snippet::io.micronaut.docs.config.immutable.ValueAddedTaxConfiguration[tags="imports,class",title="Record Example"]
<1> The `percentage` field defines a configuration property for "vat"
Record classes are constructor-based configuration and behave exactly as described above with respect to a refresh, including when annotated with ann:runtime.context.scope.Refreshable[]. Being `final` makes no difference here: no AOP proxy is generated for a ann:context.annotation.ConfigurationProperties[] class in any case.
NOTE: From a performance perspective records are better than interfaces, because an interface getter resolves its value through the `Environment` on every call whereas a record returns an already-bound field. The difference is small; let your refresh requirements drive the choice.
== Default values
You can supply a default for a property in two ways:
* the `defaultValue` member of ann:core.bind.annotation.Bindable[], for example `@Bindable(defaultValue = "Ford")`
* a constructor default parameter, in languages that support them (such as Kotlin)
Prefer ann:core.bind.annotation.Bindable[]. Its default value is written to the generated configuration metadata, so IDE completion, JSON schema validation, and generated documentation all display it. A language-level default parameter works at runtime but is invisible to that tooling.
== Using immutable configuration
Once you have prepared a type-safe configuration it can be injected into your beans like any other bean:
snippet::io.micronaut.docs.config.immutable.Engine[tags="class",indent=0,title="@ConfigurationProperties Dependency Injection"]
<1> Inject the `EngineConfig` bean
<2> Use the configuration properties
Configuration values can then be supplied when running the application. For example:
snippet::io.micronaut.docs.config.immutable.VehicleSpec[tags="start",indent=0,title="Supply Configuration"]
The above example prints: `"Ford Engine Starting V8 [rodLength=7.0]"`
== Customizing accessors
As already explained in <<configurationPropertiesAccessorsStyle, Change accessors style>>, it is also possible to customize the accessors when creating immutable configuration properties:
[source, java]
----
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.AccessorsStyle;
import io.micronaut.core.bind.annotation.Bindable;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Optional;
@ConfigurationProperties("my.engine") // <1>
@AccessorsStyle(readPrefixes = "read") // <2>
public interface EngineConfigAccessors {
@Bindable(defaultValue = "Ford")
@NotBlank
String readManufacturer(); // <3>
@Min(1L)
int readCylinders(); // <3>
@NotNull
CrankShaft readCrankShaft(); // <3>
@ConfigurationProperties("crank-shaft")
@AccessorsStyle(readPrefixes = "read") // <4>
interface CrankShaft {
Optional<Double> readRodLength(); // <5>
}
}
----
<1> The ann:context.annotation.ConfigurationProperties[] annotation takes the configuration prefix and is declared on an interface
<2> The ann:core.annotation.AccessorsStyle[] annotation defines the `readPrefixes` as `read`.
<3> The getters are all prefixed with `read`.
<4> Nested immutable configuration can also be annotated with ann:context.annotation.ConfigurationProperties[].
<5> The getter is prefixed with `read`.
---
### Src Main Docs Guide Config Jmx (`src/main/docs/guide/config/jmx.adoc`)
Micronaut framework provides basic support for JMX.
For more information, see the https://micronaut-projects.github.io/micronaut-jmx/latest/guide/[documentation] for the micronaut-jmx project.
---
### Src Main Docs Guide Config PropertySource (`src/main/docs/guide/config/propertySource.adoc`)
Additional api:io.micronaut.context.env.PropertySource[] instances can be added to the environment prior to initializing the api:context.ApplicationContext[].
snippet::io.micronaut.docs.context.env.EnvironmentSpec[tags="envProps",indent=0,title="Initializing the Environment"]
The link:{api}/io/micronaut/context/env/PropertySource.html[PropertySource.of] method can be used to create a `PropertySource` from a map of values.
Alternatively one can register a link:{api}/io/micronaut/context/env/PropertySourceLoader.html[PropertySourceLoader] by creating a `META-INF/services/io.micronaut.context.env.PropertySourceLoader` file containing a reference to the class name of the `PropertySourceLoader`.
=== Included PropertySource Loaders
Micronaut framework by default contains `PropertySourceLoader` implementations that load properties from the given locations and priority:
. Command line arguments
. Properties from `SPRING_APPLICATION_JSON` (for Spring compatibility)
. Properties from `MICRONAUT_APPLICATION_JSON`
. Java System Properties
. OS environment variables
. Configuration files loaded in order from the system property 'micronaut.config.files' or the environment variable `MICRONAUT_CONFIG_FILES`. The value can be a comma-separated list of paths with the last file having precedence. The files can be referenced from:
.. the file system as an absolute path (without any prefix),
.. the classpath with a `classpath:` prefix.
. Environment-specific properties from `application-{environment}.{extension}`
. Application-specific properties from `application.{extension}`
NOTE: 'micronaut.config.files' will be ignored in bootstrap.yml or application.yml.
[[configImport]]
=== Importing Additional Configuration
You can import additional configuration directly from a configuration file using `micronaut.config.import`.
The value can be a single string, a list, or indexed entries (`micronaut.config.import[0]`, `micronaut.config.import[1]`, ...).
[configuration,title="Importing additional configuration"]
----
micronaut:
config:
import:
- "file:///etc/myapp/shared"
- "classpath://overrides.yml"
- "optional:env://MY_APP_INLINE_CONFIG"
----
Imports are resolved recursively and support the following protocols:
* `file://` β load from the file system
* `classpath://` β load exactly one matching classpath resource (fails if duplicates are found)
* `classpath*://` β load and merge all matching classpath resources in classpath discovery order
* `env://` β load key/value properties from an environment variable value, using the variable-name suffix (for example `.yml`) or `?extension=yml` / `?extension=json` to select non-properties formats
* `configtree://` β load a directory tree where file paths map to property keys
Prefix any import with `optional:` to skip it when the target is missing.
Without `optional:`, an api:io.micronaut.context.exceptions.ConfigurationException[] is thrown if the import cannot be loaded.
For `file://`, `classpath://`, and `classpath*://` imports, if no extension is provided, Micronaut probes known configuration extensions (`.properties`, `.json`, `.yml`, and any additional enabled loaders).
For `env://` imports, Micronaut defaults to `.properties` parsing. To import YAML or JSON from an environment variable, either include the format in the variable reference such as `env://MY_APP_INLINE_CONFIG.yml`, or specify it explicitly with `env://MY_APP_INLINE_CONFIG?extension=yml`.
When multiple resources with the same classpath name are present:
* `classpath://` throws a api:io.micronaut.context.exceptions.ConfigurationException[] and reports all matching locations.
* `classpath*://` loads all matches in classpath order and merges them in that same order.
IMPORTANT: `file:///tmp/bar.properties` (with three slashes) resolves to the absolute path `/tmp/bar.properties`.
[[customPropertySourceImporter]]
=== Implementing a Custom PropertySourceImporter
You can add support for a custom import protocol by implementing api:io.micronaut.context.env.PropertySourceImporter[] and registering it with Java ServiceLoader. Each importer declares its provider with `getProvider()`, converts the parsed api:io.micronaut.core.util.ConnectionString[] into a typed declaration via `newImportDeclaration(..)`, and then reads configuration from `importPropertySource(..)`.
snippet::io.micronaut.docs.config.importer.DemoPropertySourceImporter[tags="class",indent=0,title="Custom importer implementation"]
snippet::io.micronaut.docs.config.importer.DemoPropertySourceImporterTest[tags="test",indent=0,title="Custom importer test"]
Register your implementation in `META-INF/services/io.micronaut.context.env.PropertySourceImporter`:
[source]
----
io.micronaut.docs.config.importer.DemoPropertySourceImporter
----
After registration, the importer is selected when `micronaut.config.import` uses your protocol, for example `demo://defaults`.
If your importer loads remote configuration, api:discovery.config.RetryablePropertySourceImporter[] in `micronaut-discovery-core` provides a reusable base class that standardizes retry behavior across both connection-string and map-based imports. It parses the same retry properties from either form and applies them with Micronaut's programmatic retry support.
Standard retry properties supported by api:discovery.config.RetryablePropertySourceImporter[] are:
* `retry-attempts` β maximum number of attempts
* `retry-count` β alias for `retry-attempts`
* `retry-delay` β delay between attempts
* `retry-max-delay` β maximum overall retry delay
* `retry-multiplier` β delay multiplier
* `retry-jitter` β retry jitter factor from `0.0` to `1.0`
TIP: `.properties`, `.json`, `.yml` are supported out of the box. For Groovy users `.groovy` is supported as well.
[[duplicateConfigurationResources]]
=== Duplicate Configuration Resources
If a configuration file (for example `application.properties` or `application.yml`) is present more than once on the classpath, Micronaut can be configured to:
* fail fast with a clear error describing the conflicting locations,
* take the first match (with optional warning), or
* merge all matching resources.
The behavior can be customized using the api:io.micronaut.context.ApplicationContextBuilder[] (including `Micronaut`).
snippet::io.micronaut.docs.context.env.ConfigurationLoadStrategySnippet[tags="firstMatch",indent=0,title="Using FIRST_MATCH"]
To merge duplicates, set the strategy type to `MERGE_ALL`:
snippet::io.micronaut.docs.context.env.ConfigurationLoadStrategySnippet[tags="mergeAll",indent=0,title="Merging duplicates"]
When using `MERGE_ALL`, you can optionally specify a merge order based on artifact (JAR) name patterns:
snippet::io.micronaut.docs.context.env.ConfigurationLoadStrategySnippet[tags="mergeOrder",indent=0,title="MERGE_ALL with mergeOrder"]
NOTE: `mergeOrder` is only supported when the strategy type is `MERGE_ALL`.
When resources are merged, later resources override earlier ones when the same property key is present.
Note that if you want full control of where your application loads configuration from you can disable the default `PropertySourceLoader` implementations listed above by calling the `enableDefaultPropertySources(false)` method of the api:context.ApplicationContextBuilder[] interface when starting your application.
In this case only explicit api:context.env.PropertySource[] instances that you add via the `propertySources(..)` method of the api:context.ApplicationContextBuilder[] interface will be used.
=== Supplying Configuration via Command Line
Configuration can be supplied at the command line using Gradle or our Maven plugin. For example:
[source,bash]
.Gradle
----
$ ./gradlew run --args="-endpoints.health.enabled=true -config.property=test"
----
[source,bash]
.Maven
----
$ ./mvnw mn:run -Dmn.appArgs="-endpoints.health.enabled=true -config.property=test"
----
For the configuration to be a part of the context, the args from the main method must be passed to the context builder. For example:
[source,java]
----
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args); // passing args
}
}
----
=== Secrets and Sensitive Configuration
It is important to note that it is not recommended to store sensitive configuration such as passwords and tokens within configuration files that can potentially be checked into source control systems.
It is good practise to instead externalize sensitive configuration completely from the application code using preferably an external secret manager system (there are many options here, many provided by Cloud providers) or environment variables that are set during the deployment of the application. You can also use property placeholders (see the following section), to customize names of the environment variables to use and supply default values:
.Using Property Value Placeholders to Define Secure Configuration
[configuration]
----
datasources:
default:
url: ${JDBC_URL:`jdbc:mysql://localhost:3306/db`}
username: ${JDBC_USER:root}
password: ${JDBC_PASSWORD:}
dialect: MYSQL
driverClassName: ${JDBC_DRIVER:com.mysql.cj.jdbc.Driver}
----
To securely externalize configuration consider using a secrets manager system supported by the Micronaut framework such as:
* https://micronaut-projects.github.io/micronaut-aws/latest/guide/#secretsmanager[AWS Secrets Manager].
* https://micronaut-projects.github.io/micronaut-gcp/latest/guide/#secretManager[Google Cloud Secrets Manager].
* <<distributedConfigurationVault, HashiCorp Vault>>
* https://micronaut-projects.github.io/micronaut-kubernetes/latest/guide/index.html#config-client[Kubernetes Secrets].
* https://micronaut-projects.github.io/micronaut-oracle-cloud/latest/guide/#vault[Oracle Cloud Vault].
=== Property Value Placeholders
As mentioned in the previous section, the Micronaut framework includes a property placeholder syntax to reference configuration properties both within configuration values and with any Micronaut annotation. See ann:io.micronaut.context.annotation.Value[] and the section on <<valueAnnotation,Configuration Injection>>.
TIP: Programmatic usage is also possible via the api:io.micronaut.context.env.PropertyPlaceholderResolver[] interface.
The basic syntax is to wrap a reference to a property in `${...}`. For example:
[configuration,title="Defining Property Placeholders"]
----
myapp:
endpoint: http://${micronaut.server.host}:${micronaut.server.port}/foo
----
The above example embeds references to the `micronaut.server.host` and `micronaut.server.port` properties.
You can specify default values by defining a value after the `:` character. For example:
[configuration,title="Using Default Values"]
----
myapp:
endpoint: http://${micronaut.server.host:localhost}:${micronaut.server.port:8080}/foo
----
The above example defaults to `localhost` and port `8080` if no value is found (rather than throwing an exception). Note that if the default value contains a `:` character, you must escape it using backticks:
.Using Backticks
[configuration]
----
myapp:
endpoint: ${server.address:`http://localhost:8080`}/foo
----
The above example looks for a `server.address` property and defaults to `http://localhost:8080`. This default value is escaped with backticks since it has a `:` character.
=== Property Value Binding
Note that these property references should be in kebab case (lowercase and hyphen-separated) when placing references in code or in placeholder values. For example, use `micronaut.server.default-charset` and not `micronaut.server.defaultCharset`.
The Micronaut framework still allows specifying the latter in configuration, but normalizes the properties into kebab case form to optimize memory consumption and reduce complexity when resolving properties. The following table summarizes how properties are normalized from different sources:
.Property Value Normalization
|===
|Configuration Value |Resulting Properties|Property Source
|`myApp.myStuff` | `my-app.my-stuff` | Properties, YAML etc.
|`my-app.myStuff` | `my-app.my-stuff` | Properties, YAML etc.
|`myApp.my-stuff` | `my-app.my-stuff` | Properties, YAML etc.
|`MYAPP_MYSTUFF` | `myapp.mystuff`, `myapp-mystuff` | Environment Variable
|`MY_APP_MY_STUFF` | `my.app.my.stuff`, `my.app.my-stuff`, `my.app-my.stuff`, `my.app-my-stuff`, `my-app.my.stuff`, `my-app.my-stuff`, `my-app-my.stuff`, `my-app-my-stuff` | Environment Variable
|===
Environment variables are treated specially to allow more flexibility. Note that there is no way to reference an environment variable with camel-case.
IMPORTANT: Because the number of properties generated is exponential based on the number of `_` characters in an environment variable, it is recommended to refine which, if any, environment variables are included in configuration if the number of environment variables with multiple underscores is high.
IMPORTANT: Because of the way `_` characters are treated in environment variables, it is not possible to target a poperty with `_` in the name. Per the above, properties should be in kebab case to maintain the ability to target the property with environment variables.
To control how environment properties participate in configuration, call the respective methods on the `Micronaut` builder.
snippet::io.micronaut.docs.context.Application[tags="imports,class",title="Application class"]
NOTE: The configuration above does not have any impact on property placeholders. It is still possible to reference an environment variable in a placeholder regardless of whether environment configuration is disabled, or even if the specific property is explicitly excluded.
=== Using Random Properties
You can use `random` values by using the following properties. These can be used in configuration files as variables like the following.
[configuration]
----
micronaut:
application:
name: myapplication
instance:
id: ${random.shortuuid}
----
.Random Values
|===
|Property |Value
|random.port
|An available random port number
|random.int
|Random int
|random.integer
|Random int
|random.long
|Random long
|random.float
|Random float
|random.shortuuid
|Random UUID of only 10 chars in length (Note: As this isn't full UUID, collision COULD occur)
|random.uuid
|Random UUID with dashes
|random.uuid2
|Random UUID without dashes
|===
The `random.int`, `random.integer`, `random.long` and `random.float` properties supports a range suffix whose syntax is one of as follows:
- `(max)` where max is an exclusive value
- `[min,max]` where min being inclusive and max being exclusive values.
[configuration]
----
instance:
id: ${random.int[5,10]}
count: ${random.int(5)}
----
NOTE: The range could vary from negative to positive as well.
=== Fail Fast Property Injection
For beans that inject required properties, the injection and potential failure will not occur until the bean is requested. To verify at startup that the properties exist and can be injected, the bean can be annotated with ann:io.micronaut.context.annotation.Context[]. Context-scoped beans are injected at startup, and startup fails if any required properties are missing or cannot be converted to the required type.
IMPORTANT: It is recommended to use this feature sparingly to ensure fast startup.
---
### Src Main Docs Guide Config ValueAnnotation (`src/main/docs/guide/config/valueAnnotation.adoc`)
You can inject configuration values into beans using the ann:io.micronaut.context.annotation.Value[] annotation.
=== Using the `@Value` Annotation
Consider the following example:
snippet::io.micronaut.docs.config.value.EngineImpl[tags="imports,class",indent=0,title="@Value Example"]
<1> The `@Value` annotation accepts a string that can have embedded placeholder values (the default value can be provided by specifying a value after the colon `:` character). Also try to avoid setting the member visibility to `private`, since this requires the Micronaut Framework to use reflection. Prefer to use `protected`.
<2> The injected value can then be used within code.
Note that `@Value` can also be used to inject a static value. For example the following injects the number 10:
.Static @Value Example
[source,groovy]
----
@Value("10")
int number;
----
This is even more useful when used to compose injected values combining static content and placeholders. For example to set up a URL:
.Placeholders with @Value
[source,groovy]
----
@Value("http://${my.host}:${my.port}")
URL url;
----
In the above example the URL is constructed from two placeholder properties that must be present in configuration: `my.host` and `my.port`.
Remember that to specify a default value in a placeholder expression, you use the colon `:` character. However, if the default you specify includes a colon, you must escape the value with backticks. For example:
.Placeholders with @Value
[source,groovy]
----
@Value("${my.url:`http://foo.com`}")
URL url;
----
Note that there is nothing special about `@Value` itself regarding the resolution of property value placeholders.
Due to Micronaut's extensive support for annotation metadata you can use property placeholder expressions on any annotation. For example, to make the path of a `@Controller` configurable you can do:
[source,java]
----
@Controller("${hello.controller.path:/hello}")
class HelloController {
...
}
----
In the above case, if `hello.controller.path` is specified in configuration the controller will be mapped to the specified path, otherwise it will be mapped to `/hello`.
You can also make the target server for ann:io.micronaut.http.client.annotation.Client[] configurable (although service discovery approaches are often better), for example:
[source,java]
----
@Client("${my.server.url:`http://localhost:8080`}")
interface HelloClient {
...
}
----
In the above example the property `my.server.url` can be used to configure the client, otherwise the client falls back to a localhost address.
=== Using the `@Property` Annotation
Recall that the ann:io.micronaut.context.annotation.Value[] annotation receives a String value which can be a mix of static content and placeholder expressions. This can lead to confusion if you attempt to do the following:
.Incorrect usage of `@Value`
[source,groovy]
----
@Value("my.url")
String url;
----
In the above case the literal string value `my.url` is injected and set to the `url` field and *not* the value of the `my.url` property from your application configuration. This is because `@Value` only resolves placeholders within the value specified to it.
To inject a specific property name, you may be better off using ann:io.micronaut.context.annotation.Property[]:
snippet::io.micronaut.docs.config.property.Engine[tags="imports,class",indent=0, title="Using @Property"]
<1> The `my.engine.cylinders` property is resolved from configuration and injected into the field.
<2> Fields subject to injection should not be private because expensive reflection must be used
<3> The `@Property` annotation is used to inject through the setter
NOTE: Because it is not possible to define a default value with `@Property`, if the value doesn't exist or cannot be converted to the required type, bean instantiation will fail.
The above instead injects the value of the `my.engine.cylinders` property resolved from application configuration. If the property cannot be found in configuration, an exception is thrown. As with other types of injection, the injection point can also be annotated with `@Nullable` to make the injection optional.
You can also use this feature to resolve sub maps. For example, consider the following configuration:
[configuration]
----
datasources:
default:
name: 'mydb'
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update
show_sql: true
----
To resolve a flattened map containing only the properties starting with `hibernate`, use `@Property`, for example:
.Using `@Property`
[source,java]
----
@Property(name = "jpa.default.properties")
Map<String, String> jpaProperties;
----
The injected map will contain the keys `hibernate.hbm2ddl.auto` and `hibernate.show_sql` and their values.
TIP: The ann:io.micronaut.core.convert.format.MapFormat[] annotation can be used to customize the injected map depending on whether you want nested keys or flat keys, and it allows customization of the key style via the api:io.micronaut.core.naming.conventions.StringConvention[] enum.
---
### Src Main Docs Guide Configurations DataAccess (`src/main/docs/guide/configurations/dataAccess.adoc`)
This table summarizes the configuration modules and dependencies to add to your build to enable them:
.Data Access Configuration Modules
|===
|Dependency|Description
|`io.micronaut.sql:micronaut-jdbc-dbcp`
|Configures SQL link:{jdkapi}/java.sql/javax/sql/DataSource.html[DataSource]s using https://commons.apache.org/proper/commons-dbcp/[Commons DBCP]
|`io.micronaut.sql:micronaut-jdbc-hikari`
|Configures SQL link:{jdkapi}/java.sql/javax/sql/DataSource.html[DataSource]s using https://github.com/brettwooldridge/HikariCP[Hikari Connection Pool]
|`io.micronaut.sql:micronaut-jdbc-tomcat`
|Configures SQL link:{jdkapi}/java.sql/javax/sql/DataSource.html[DataSource]s using https://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html[Tomcat Connection Pool]
|`io.micronaut.sql:micronaut-hibernate-jpa`
|Configures Hibernate/JPA `EntityManagerFactory` beans
|`io.micronaut.mongodb:micronaut-mongo-reactive`
|Configures the https://mongodb.github.io/mongo-java-driver-reactivestreams[MongoDB Reactive Driver]
|`io.micronaut.neo4j:micronaut-neo4j-bolt`
|Configures the https://github.com/neo4j/neo4j-java-driver[Bolt Java Driver] for https://neo4j.com[Neo4j]
|`io.micronaut.sql:micronaut-vertx-mysql-client`
|Configures the https://github.com/eclipse-vertx/vertx-sql-client/tree/master/vertx-mysql-client[Reactive MySQL Client]
|`io.micronaut.sql:micronaut-vertx-pg-client`
|Configures the https://github.com/eclipse-vertx/vertx-sql-client/tree/master/vertx-pg-client[Reactive Postgres Client]
|`io.micronaut.redis:micronaut-redis-lettuce`
|Configures the https://lettuce.io[Lettuce] driver for https://redis.io[Redis]
|`io.micronaut.cassandra:micronaut-cassandra`
|Configures the https://github.com/datastax/java-driver[Datastax Java Driver] for https://cassandra.apache.org[Cassandra]
|===
For example, to add support for MongoDB, add the following dependency:
.build.gradle
[source,groovy]
----
compile "io.micronaut.mongodb:micronaut-mongo-reactive"
----
The following sections go into more detail about configuration options and the exposed beans for each implementation.
---
### Src Main Docs Guide Configurations ReactiveConfigs (`src/main/docs/guide/configurations/reactiveConfigs.adoc`)
https://projectreactor.io[Project Reactor] is used internally by Micronaut. However, to use Reactor or other reactive libraries (e.g. RxJava) types in your controller and/or HTTP Client methods you need to include dependencies.
---
### Src Main Docs Guide ContextPropagation HttpFilterContextPropagation (`src/main/docs/guide/contextPropagation/httpFilterContextPropagation.adoc`)
Modifying the propagated context is a common scenario. Usually, you want to extend the context to include the request-related values.
To use a non-reactive HTTP filter API, you need to add a method parameter api:core.propagation.MutablePropagatedContext[] and modify the propagated context elements by adding or removing the existing ones:
.Example of adding a new MDC propagated context element
[source,java]
----
include::{testsuitejava}/propagation/MdcFilter.java[tags="class", indent=0]
----
The next filter in the chain will have the new propagated context available. Any of the thread-local context elements will be set for the next filter or the controller method invocation.
To use the legacy reactive HTTP filters, simply modify and propagate the context bound to the following chain invocation:
.Example of adding a new MDC propagated context element for the reactive filter:
[source,java]
----
include::{testsuitejava}/propagation/MdcLegacyFilter.java[tags="class", indent=0]
----
NOTE: The context added by a filter only covers the work that happens *after* that filter runs.
Framework log statements emitted earlier in the request - for example when the request is received - do not see it.
An ordinary server filter also runs after the route has been matched, so route matching is not covered either.
If you need the context in place for route matching, add it from a filter annotated with api:http.server.annotation.PreMatching[].
---
### Src Main Docs Guide ContextPropagation ReactorContextPropagation (`src/main/docs/guide/contextPropagation/reactorContextPropagation.adoc`)
Since Micronaut Framework version 4, https://projectreactor.io[Project Reactor] integration no longer captures the state automatically. Micronaut Framework users need to extend the propagation context manually.
Before version 4, Micronaut Framework required the instrumentation of every reactive operator to capture the current state to propagate it. It added an unwanted overhead and forced us to maintain complicated Reactor operators' instrumentation.
Since 3.5.0, Reactor-Core embeds support for the `io.micrometer:context-propagation` SPI. This allows to achieve the same thread-local propagation by including the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation] dependency.
The framework automatically adds the `PropagatedContext` to Project Reactor's context for interceptors and the HTTP filters. You can access it via the utility class api:core.async.propagation.ReactorPropagation[].
NOTE: api:core.async.propagation.ReactorPropagation[] is an experimental class and might change in the future.
It is possible to use https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation], which Reactor supports for propagation and restoring the thread-local context.
To enable it, include the dependency:
dependency:context-propagation[groupId="io.micrometer",scope="compile"]
After that, all the thread-local propagated elements can restore their thread-local value.
NOTE: The thread-local values are read-only. To modify them, the `PropagatedContext` instance needs to be changed and put into the Reactor's context.
To add the context in the middle of the reactive chain, do something like the following:
snippet::io.micronaut.docs.propagation.reactor.HelloController[tags="imports,example", indent=0]
<1> Obtain the current context that has been modified, e.g. with `PropagatedContext.get().plus(...)`, etc.
<2> Add the context into the reactive chain.
If you have Micrometer Context Propagation on the classpath but don't want to use it, apply the following configuration:
.Disable Micrometer Context Propagation in Reactor
[configuration]
----
reactor:
enable-automatic-context-propagation: false
----
---
### Src Main Docs Guide DevMode DevModeLaunch (`src/main/docs/guide/devMode/devModeLaunch.adoc`)
== The manifest
The launcher reads a manifest, a `java.util.Properties` file the build tool writes. The manifest names:
* the main class;
* the directories holding the compiled classes and resources, which form the reloadable tier;
* the compile and annotation processor classpaths;
* the source directories, by language;
* the resource directories, by kind.
[source,properties]
----
micronaut.dev.main-class=example.Application
micronaut.dev.reloadable=build/classes/java/main,build/resources/main
micronaut.dev.compile-classpath=@build/micronaut-dev/compile.argfile
micronaut.dev.processor-path=@build/micronaut-dev/processors.argfile
micronaut.dev.sources.java=src/main/java
micronaut.dev.resources.config=src/main/resources
micronaut.dev.resources.views=src/main/resources/views
micronaut.dev.resources.static=src/main/resources/static
micronaut.dev.compile.java.output=build/classes/java/main
micronaut.dev.retain=javax.sql.DataSource
----
The launcher reads the entries as follows:
* Relative paths resolve against the manifest's directory.
* `@file` reads a list from an argument file.
* A system property of the same name overrides an entry.
On macOS, put `micronaut-runtime-osx` on the development runtime classpath as well, which the build plugins do. The launcher then watches through the native file system events instead of the JDK's polling service, and a save is noticed at once rather than seconds later.
== Starting the launcher
The launcher is started with the runtime classpath, which holds the framework, the libraries and `micronaut-dev` itself. It does not hold the project's own class directories: those belong to the reloadable tier.
[source,bash]
----
java -cp @build/micronaut-dev/runtime.argfile io.micronaut.dev.MicronautDevMain --manifest build/micronaut-dev/dev.properties
----
The launcher compiles every language whose class output does not exist yet, so it works from a clean checkout. It then runs the application's own `main` on a thread of its own.
The application does what it always does, `Micronaut.run(Application.class, args)`. An `ApplicationContextConfigurer` the module ships gives the context:
* the reloadable loader;
* the `dev` environment;
* `micronaut.dev.enabled=true`;
* the beans retained from the previous generation.
TIP: Everything under the manifest's `micronaut.dev.` keys can be set as a system property. api:context.env.DevelopmentMode[] answers whether the application runs this way, so a bean can be conditioned on it with `@DevelopmentActive` (or `@DevelopmentInactive` for the opposite).
== The dev endpoint
With `micronaut-management` on the classpath, the application has a `dev` endpoint:
* `GET /dev` reports the generation, the reload strategy, the watched roots and the last compilation failure.
* `POST /dev/reload` compiles everything and restarts if anything changed. It is the way to recover when the watcher missed an edit, and the gate that answers requests with a compilation failure lets it through.
The endpoint moves with `endpoints.all.path` and `endpoints.dev.path`, like any other.
The endpoint is sensitive: its status holds absolute paths of the machine, and its reload compiles and restarts the application. It therefore answers 401 until Micronaut Security protects it, or until a development configuration opts out:
[configuration]
----
endpoints:
dev:
sensitive: false
----
The reload is a JSON request. It is refused with a 403 when a browser marks it as coming from another site, or when its `Origin` is not the server's own: `localhost`, a `*.localhost` name or the loopback address, on the port the request arrived on. The `Host` the request names plays no part, since a page of another site can make its own name resolve to the machine. A page the developer visits therefore cannot trigger it with a form; a client that is no page, such as curl or an IDE, sends no `Origin`:
[source,bash]
----
curl -X POST -H 'Content-Type: application/json' http://localhost:8080/dev/reload
----
== Compilers
The launcher compiles each language with a compiler of its own, all of them incrementally. The changed sources and every source whose classes reference them are compiled together against the previous output, and the outputs replace the old ones only when the compilation succeeded.
* Java compiles through the JDK's own `javac`, with the annotation processors of the processor path.
* Groovy compiles through the project's own Groovy on the launch classpath, with the Micronaut AST transformations the compile classpath carries.
* Kotlin compiles through the Kotlin Build Tools API when `kotlin-build-tools-impl` is on the launch classpath at the project's Kotlin version. It runs the symbol processors of the processor path first when `symbol-processing-aa-embeddable` is there at the project's KSP version. The `-A` options of the language pass to the processors, and the other options to `kotlinc`.
The build plugins put these artifacts on the launch classpath for the languages the project uses, so `micronaut-dev` itself depends on neither Kotlin nor Groovy.
Python compiles through the Pyronaut compiler of `micronaut-inject-python` when it is on the launch classpath:
* The compiler generates the Java stubs and bean definitions of the Python modules, and compiles them in one `javac` run with the module's Java sources, since each references the other. When the Java sources share the Python class output, the Python compiler compiles them too, with the options of both languages, and `javac` never runs on them alone.
* The compiler plans its incremental work itself and keeps its state beside the class output. In one transaction, it then copies what changed into the class output and the generated stubs into the generated sources directory, and removes what it no longer produces.
The Python modules are resources of the class output, so a Python edit starts a new generation, whose GraalPy context imports the new modules; the GraalPy engine stays loaded.
Reloading needs a JVM: a native image can neither define the classes of a new generation nor redefine a class, so the native toolchain of Pyronaut restarts the process instead.
== LiveReload
Add `micronaut-dev-livereload` to the development runtime classpath and the launcher starts a LiveReload server on port 35729 (`micronaut.dev.livereload.port`). Adding the module is the switch: there is no other setting to turn it on. Browsers then:
* reload when the application restarted or a template changed;
* swap a stylesheet in place when only a stylesheet changed.
Pages get the client script appended before their closing body tag, except a page with a Content-Security-Policy, in a header or a `<meta http-equiv>` element, which would block it; set `micronaut.dev.livereload.inject-script=false` if you use a browser extension. A api:dev.livereload.LiveReloadTrigger[] bean lets a module ask for a refresh.
The server listens on the loopback address only, and its socket accepts the clients LiveReload is made for:
* native clients, which send no `Origin`;
* the browser extensions (`chrome-extension:`, `moz-extension:` and `safari-web-extension:` origins);
* pages served by `localhost`, a `*.localhost` name or the loopback address, on any port.
A page of any other site is refused, so that a site the developer visits cannot follow the reloads. This holds for the script a browser extension injects too, since it runs in the page and connects with the page's origin. Open the application through `localhost`, the loopback address or a name under `.localhost`, such as `myapp.localhost`, which browsers resolve to the loopback address.
=== Serving other pages
The server also serves pages that are not the application's, such as a test report:
* `LiveReloadServer.serve(prefix, directory)` mounts a directory at an `http://localhost:35729` address, with the client script in its HTML. Nothing outside the directory is served.
* A page that follows something as it happens subscribes to a topic of the event channel, `ws://localhost:35729/micronaut-dev/events?topic=<topic>`, and receives the JSON that `LiveReloadServer.publish(topic, json)` sends. The channel is apart from the LiveReload socket, whose clients reject commands they do not know.
A component reaches the server through `DevRuntime.current().liveReload()`, in test mode too.
---
### Src Main Docs Guide DevMode DevModeReload (`src/main/docs/guide/devMode/devModeReload.adoc`)
A change is handled as one batch: files saved together, across several directories, are compiled and applied together. What the batch does depends on what changed.
== Sources
The changed files are compiled, together with every source whose classes link against a changed or deleted class. An index of which classes reference which finds those sources. They compile with the compile classpath, the annotation processors and the options the build uses.
The class files and generated sources go to a staging directory, and replace the output only when the compilation succeeded. A broken edit therefore leaves the running application as it is:
* the errors are logged;
* when an HTTP server runs, the errors are shown to a browser in place of the page, until the next successful compilation.
== Classes
When a class file changed, the application restarts on a new generation:
. The context of the previous generation is stopped, retaining what a api:context.reload.BeanRetentionPolicy[] asks for.
. A new generation of the class loader is created over a fresh snapshot of the output.
. The application's `main` runs again.
The retained singletons are adopted by the new context under their definitions, so a bean that depends on them is created on top of the same instance.
Two events frame the restart: api:context.reload.ClassChangeEvent[] is published to the old context before it stops, and api:context.reload.ReloadCompletedEvent[] to the new one once it started.
== Method bodies
With `micronaut.dev.strategy=auto` (the default) or `reload`, an edit that changes only method bodies is applied in place, without a restart. An edit qualifies when the structure of every changed class is the same and no generated class changed. The JVM redefines the loaded classes through the agent, and the running beans see the new bodies, in tens of milliseconds.
The agent is `micronaut-dev` itself, launched with `-javaagent`, which the build plugins add. Without it, or with `strategy=restart`, every change restarts.
== Resources
A changed template, static file or message bundle does not restart anything: it reaches the beans that watch resources of that kind (see the next section).
A changed configuration file is applied in place, without a new generation. The configuration is refreshed: the configuration beans are bound again, the refreshable beans are disposed of, and the configuration watches are told.
The application restarts only when the refresh cannot apply the change:
* a configuration watch answers `REQUIRES_RESTART`;
* a singleton received a changed property through `@Value` or `@Property` rather than a configuration bean;
* a definition's `@Requires` names a changed property, whose presence the change may have flipped.
A bean retained across restarts that watched a changed prefix is not kept when that restart comes.
== Retaining a bean
Two mechanisms say which beans are kept across restarts:
* The manifest's `micronaut.dev.retain` names the types to keep.
* A module can contribute a api:context.reload.BeanRetentionPolicy[] bean instead, which is consulted for every singleton when the context stops.
A bean is only retained when its class and everything it received, as the dependency graph records, come from the runtime classpath rather than the reloadable tier. A bean that holds an application class cannot survive a change to that class.
A module declares which of its beans are retained with api:context.annotation.Retain[], on the bean's class or on the factory method that produces it. A module that defines a connection pool does so, so that a restart keeps the pool, and an in-memory database with it. The bean is dropped and created again when the configuration under one of the prefixes its `invalidatedBy` names changes.
A configuration bean the retained bean received under one of those prefixes, such as its `@ConfigurationProperties`, does not keep it from being retained, and is not retained with it. Each generation binds it again, so the retained bean must copy the values it needs rather than keep the configuration bean.
Set `micronaut.dev.retain-annotated=false` to retain only what `micronaut.dev.retain` names.
Two cases concern pools in particular:
* A pool that a `BeanCreatedEventListener` wraps, as Micronaut Data wraps a data source, is retained as the listener received it, and wrapped again by the new context's listeners.
* A pool is not retained when the factory that made it holds the application context, since destroying the factory would close it. The log says why.
== What the console shows
The console shows what each reload did: the generation started, the time it took, the classes changed and the beans retained.
Once the new generation serves, the runtime checks that generations older than two are no longer reachable, and warns when one is. A static cache in application code or a thread that was never stopped is the usual cause.
---
### Src Main Docs Guide DevMode DevModeTest (`src/main/docs/guide/devMode/devModeTest.adoc`)
In test mode the development runtime runs a project's tests instead of its application. It watches the sources, compiles a change in process, and runs the tests the change can affect on a new generation, in the same JVM. The library jars, the compilers, the JIT and expensive parent-tier state therefore stay warm from one run to the next.
The build plugins start it with `./gradlew mnTest` and `mvn mn:test`.
== The manifest
The manifest switches the mode and names the test sources, their output, and how they run:
[source,properties]
----
micronaut.dev.mode=test
micronaut.dev.test.sources.java=src/test/java
micronaut.dev.test.compile.java.output=build/classes/java/test
micronaut.dev.test.resources.config=src/test/resources
micronaut.dev.test.runner=junit-platform
micronaut.dev.test.selection=affected
micronaut.dev.test.reports=build/test-results/mnTest
micronaut.dev.test.filter=com.example.*Test
----
The test sources compile with the application's compilers, against the application's output. Both outputs are the reloadable tier, so a test and the classes it tests always come from one generation.
Test-only libraries, JUnit and `micronaut-test` among them, are on the launch classpath. Without `test.compile-classpath` or `test.processor-path`, the tests compile with the application's.
== Which tests run
A change runs the test classes that reference a changed class, directly or through other classes, as the class files tell, together with the classes that failed in their last run.
Every test runs:
* after a change of resources or configuration;
* when a changed class declares compile-time constants, which javac inlines without a reference to follow;
* with `test.selection=all`.
A change that does not compile runs nothing and is reported, and the last results stand.
== The console
On a terminal, single keys ask for runs:
* space runs the last tests again;
* `a` runs every test;
* `f` runs the failures;
* `w` turns watching on or off;
* `q` exits with the last run's status.
With `test.once=true` the tests run once, and the launcher exits with their status.
== Test runners and reports
Test execution is pluggable: a `TestRunner` registered as a service and named by `test.runner` runs the selected tests and streams their events.
The JUnit Platform runner is built in. It runs every engine on the classpath, from Jupiter and Spock to Pyronaut's pytest engine, when `junit-platform-launcher` is on the launch classpath.
The events become JUnit XML reports, one `TEST-<class>.xml` per class in the Surefire shape, which every CI server and IDE reads. They also go to every `TestReportListener` registered as a service, such as an HTML report.
---
### Src Main Docs Guide DevMode DevModeWatch (`src/main/docs/guide/devMode/devModeWatch.adoc`)
A bean that derives state from other beans, from configuration or from files registers a watch for the specific things it needs, on api:context.WatchableBeanContext[]. It receives one batch whenever they change.
A watch is described with a fluent request, in the style of the file watcher: the request selects what is watched, and a terminal operation registers the watch. Two rules hold for every watch:
* The state when the watch is registered is the first batch, so a watcher has no separate startup path.
* A watch registered while a bean is being created belongs to that bean, and closes with it.
NOTE: api:context.WatchableBeanContext[] and the types of the `io.micronaut.context.watch` package are public API for modules, marked `@Experimental`. A module that derives state from the context watches it here rather than listening for events, and the API may still change in a minor release. The default context is the only implementation: a module tests for it with `instanceof`, and watches nothing when given another context.
snippet::io.micronaut.docs.devmode.watch.RouteTable[tags="imports,class", indent=0, title="Watching the bean definitions of a type"]
<1> The request selects the definitions of `RouteBuilder`, and the terminal operation registers the watcher.
<2> `change.current()` is the set after the batch; `change.added()` and `change.removed()` name what moved.
== The requests
* `definitions(Type.class)`, or `definitions()` for every bean, selects bean definitions, narrowed with `qualifier(..)` and `stereotype(..)`:
** Its terminal operations watch the definitions themselves.
** `instances()` continues it as a watch of the beans of those definitions, which it creates.
** `methods(Annotation.class)` continues it as a watch of their executable methods carrying an annotation.
** `@EachProperty` and `@EachBean` beans are selected one per entry, as the context resolves them.
* `methods(Annotation.class)` watches the executable methods of every bean carrying an annotation: the reload-aware form of an `ExecutableMethodProcessor`.
* `configuration(prefix)` watches a configuration prefix, and `configuration()` every change.
* `resources(ResourceKind.VIEWS).include("**/*.html")` watches the files of a resource kind, by glob.
* `classChanges()` watches the class changes of a reload.
A definition or method that comes back in a new generation is paired with the one it replaces, and the pair says whether only bodies changed.
== Batches and per-change handlers
The batch is the unit of delivery. A reload retires one generation's definitions and adds the next one's in a single batch, so a watcher that aggregates what it selects, like the route table above, never sees a half-applied reload.
A watcher that handles one change at a time adds per-change handlers to the request instead. They are called for each change once its batch is applied:
* `onAdded`;
* `onRemoved`;
* `onReplaced`, for a new generation of the same bean or method, which is otherwise a removal followed by an addition.
snippet::io.micronaut.docs.devmode.watch.CodecRegistry[tags="handlers", indent=0, title="Handling one change at a time"]
== Watching configuration
A configuration watcher runs after the configuration beans under its prefix were bound again. There are two kinds:
* A plain `ConfigurationWatcher`, registered with `watch(..)`, applies the change.
* A watcher that cannot always apply it is a `ReloadingConfigurationWatcher`, registered with `watchReloading(..)`. It says what it did:
** `APPLIED` when it changed the live bean;
** `RECREATE` when the bean that registered the watch must be replaced, in which case the context replaces it and the beans that received it;
** `REQUIRES_RESTART` when nothing short of a restart applies the change;
** `IGNORED`.
In the following example, `ConnectionPool` stands in for a pool such as Hikari's, made for each entry under `pools`:
snippet::io.micronaut.docs.devmode.watch.ConnectionPoolFactory[tags="imports,class", indent=0, title="A watcher that applies what it can"]
<1> The watch is registered while the pool is created, so it belongs to the pool.
<2> A new URL needs a new pool: the context replaces this pool only, and the beans that received it.
<3> New credentials are applied to the live pool.
<4> Any other change under the prefix leaves the pool as it is.
A configuration watch has no first batch unless it asks for one. `configuration(prefix).withFirstBatch()` calls the watcher once with a change whose `initial()` holds, so that the bean reads its configuration inside the watch. As every first batch is, the call is delivered at once when the context is running, or when it starts for a watch registered before. What a reloading watcher answers to that call is not acted on.
== The first batch and the order of changes
A bean that reads the definitions or configuration it derives state from, and only then registers a watch, can miss a change made in between. The first batch closes that gap: it is read after the watch is registered, so a bean that builds its state from the first batch, instead of from a read of its own, misses nothing.
* Batches reach a watcher one at a time, the first batch first, then the changes in the order they were made. No change is delivered before the first batch or alongside another, and no lock is held while a watcher runs.
* A change applied before the first batch began to be read shows in it, and is not delivered again. One applied after it was read is delivered after it.
* Only a change applied while the first batch was being read can show in it and be delivered after it too. A watcher therefore treats an addition it already has, or a removal of something it never had, as nothing to do.
snippet::io.micronaut.docs.devmode.watch.SerializerRegistry[tags="imports,class", indent=0, title="Building state from the first batch"]
== Class changes
A cache keyed by class is not a set of beans: it forgets what a reload retires. `classChanges()` gives the watcher every api:context.reload.ClassChangeEvent[] the launcher publishes, ahead of the listeners of the event, with the same ownership as the other watches.
Classes only change in development mode. Outside it the watch registers nothing and is returned inactive, so the module needs no listener bean gated on development mode.
snippet::io.micronaut.docs.devmode.watch.TypeDescriptionCache[tags="watch", indent=0, title="Evicting the classes a reload retires"]
== Recreating a bean
When a change cannot be applied to a bean in place, the watcher asks the context to recreate it, with `recreate(bean)`, or `recreate(Type.class, qualifier)` for the singleton of a type. Recreating a bean:
. destroys the beans that received it, as the dependency graph records them;
. destroys the bean and creates it again;
. creates the dependents again when next requested, on top of the new instance. An `ExecutableMethodProcessor` among them is created again at once, and given the methods it was given at startup.
This is more than destroying the bean and getting it again, which would leave its dependents holding the destroyed instance.
Both methods return whether anything was recreated, which a prototype, a bean of a custom scope or a singleton registered at runtime never is.
They are meant for development-time watches. The dependents are known only when the context tracks bean dependencies, which by default only a context in development mode does; `ApplicationContextBuilder.beanDependencyTrackingEnabled(true)` turns it on elsewhere. A context that does not track them recreates nothing and returns false.
snippet::io.micronaut.docs.devmode.watch.SerializerLookup[tags="imports,class", indent=0, title="Recreating a bean that holds retired instances"]
<1> When a serializer belongs to the retired generation, the lookup asks for itself to be recreated. The beans that received it are created again on top of the new lookup.
== Resources
Resources are watched the same way: a template engine registers `resources(ResourceKind.VIEWS).include("**/*.html").watch(change -> ...)` and clears what the change affects, rather than filtering an event every engine receives.
The build tools give `src/main/resources` as the configuration root, so a file kept there, such as a GraphQL schema, is watched with `resources(ResourceKind.CONFIG).include("**/*.graphqls")`. Every change under that root reaches the watches of `CONFIG`, the configuration files among them. Only a configuration file refreshes the configuration: a file the environment looked for when it read its configuration, found or not, as `Environment.getConfigurationFiles()` lists them. These are the `application` and `bootstrap` files, plain or for an environment such as `application-dev.yml`, of each configuration location, in each format a property source loader reads, the files `micronaut.config.files` names, and the files a `micronaut.config.import` imports, an optional import of a file created later included.
NOTE: Existing api:context.processor.ExecutableMethodProcessor[] and api:context.processor.BeanDefinitionProcessor[] beans keep working. The context feeds them what is added after startup, through an adapter that sees additions only, which is also how the launcher tells that such a processor cannot follow a reload.
== Testing a module against a reload
`micronaut-dev-tck` is a small TCK a module runs in its own build. Add `micronaut-dev-tck` and `micronaut-inject-java` to the test classpath to use it. A harness:
. writes a project into a directory;
. compiles it with the annotation processor on the test classpath;
. starts it through the development runtime, in the test's JVM;
. reloads it after an edit.
A test therefore sees two real generations. The module under test is on the test classpath, so it is parent-tier, as it is for an application; the sources the test writes are the reloadable tier.
snippet::io.micronaut.docs.devmode.tck.GreetingRegistryReloadTest[tags="harness", indent=0, title="Reloading a module's registry"]
The sources the test writes are Java, which the harness compiles. Here each generation's `Greeter` implements a type of the module under test:
snippet::io.micronaut.docs.devmode.tck.GreetingRegistryReloadTest[tags="source", indent=0, title="The source of each generation"]
api:dev.tck.ReloadTck[] holds the assertions:
* `assertFollowsReload` checks that what a registry hands out after the reload is the new generation's, and names the fix when it is not: watch the definitions, or resolve through the context on use.
* `assertRetained` checks that a bean the module retains is the same instance the new generation serves.
* `assertRetiredGenerationsCollected` checks that no retired generation is reachable once the garbage collector ran, which a static cache or an unstopped thread would prevent.
---
### Src Main Docs Guide HttpClient ClientAnnotation (`src/main/docs/guide/httpClient/clientAnnotation.adoc`)
Now that you have an understanding of the workings of the lower-level HTTP client, let's take a look at Micronaut's support for declarative clients via the api:http.client.annotation.Client[] annotation.
Essentially, the `@Client` annotation can be declared on any interface or abstract class, and through the use of <> the abstract methods are implemented for you at compile time, greatly simplifying the creation of HTTP clients.
Let's start with a simple example. Given the following class:
snippet::io.micronaut.docs.annotation.Pet[tags="class", indent=0, title="Pet.java"]
You can define a common interface for saving new `Pet` instances:
snippet::io.micronaut.docs.annotation.PetOperations[tags="imports, class", indent=0, title="PetOperations.java"]
Note how the interface uses Micronaut's HTTP annotations which are usable on both the server and client side. You can also use `jakarta.validation` constraints to validate arguments.
TIP: Be aware that some annotations, such as api:http.annotation.Produces[] and api:http.annotation.Consumes[], have different semantics between server and client side usage. For example, `@Produces` on a controller method (server side) indicates how the method's *return value* is formatted, while `@Produces` on a client indicates how the method's *parameters* are formatted when sent to the server. While this may seem a little confusing, it is logical considering the different semantics between a server producing/consuming vs a client: a server consumes an argument and *returns* a response to the client, whereas a client consumes an argument and *sends* output to a server.
Additionally, to use the `jakarta.validation` features, add the `validation` module to your build:
dependency:micronaut-validation[]
On the server-side of the Micronaut framework you can implement the `PetOperations` interface:
snippet::io.micronaut.docs.annotation.PetController[tags="imports, class", indent=0, title="PetController.java"]
You can then define a declarative client in `src/test/java` that uses `@Client` to automatically implement a client at compile time:
snippet::io.micronaut.docs.annotation.PetClient[tags="imports, class", indent=0, title="PetClient.java"]
<1> The api:http.client.annotation.Client[] annotation is used with a value relative to the current server, in this case `/pets`
<2> The interface extends from `PetOperations`
<3> The `save` method is overridden. See the warning below.
WARNING: Notice in the above example we override the `save` method. This is necessary if you compile without the `-parameters` option since Java does not retain parameters names in bytecode otherwise. Overriding is not necessary if you compile with `-parameters`. In addition, when overriding methods you should ensure any validation annotations are declared again since these are not link:{jdkapi}/java.base/java/lang/annotation/Inherited.html[Inherited] annotations.
Once you have defined a client you can `@Inject` it wherever you need it.
Recall that the value of `@Client` can be:
* An absolute URI, e.g. `https://api.twitter.com/1.1`
* A relative URI, in which case the server targeted is the current server (useful for testing)
* A service identifier. See the section on <<serviceDiscovery, Service Discovery>> for more information on this topic.
In production, you typically use a service ID and <<serviceDiscovery, Service Discovery>> to discover services automatically.
Another important thing to notice regarding the `save` method in the example above is that it returns a rx:Single[] type.
This is a non-blocking reactive type - typically you want your HTTP clients to not block. There are cases where you may want an HTTP client that does block (such as in unit tests), but this is rare.
The following table illustrates common return types usable with ann:http.client.annotation.Client[]:
.Micronaut Response Types
|===
|Type|Description|Example Signature
|rs:Publisher[]
|Any type that implements the rs:Publisher[] interface
|`Flux<String> hello()`
|api:http.HttpResponse[]
|An api:http.HttpResponse[] and optional response body type
|`Mono<HttpResponse<String>> hello()`
|rs:Publisher[]
|A rs:Publisher[] implementation that emits a POJO
|`Mono hello()`
|link:{jdkapi}/java.base/java/util/concurrent/CompletableFuture.html[CompletableFuture]
|A Java `CompletableFuture` instance
|`CompletableFuture<String> hello()`
|link:{jdkapi}/java.base/java/lang/CharSequence.html[CharSequence]
|A blocking native type. Such as `String`
|`String hello()`
|T
|Any simple POJO type.
|`Book show()`
|===
Generally, any reactive type that can be converted to the rs:Publisher[] interface is supported as a return type, including (but not limited to) the reactive types defined by RxJava 1.x, RxJava 2.x, and Reactor 3.x.
Returning link:{jdkapi}/java.base/java/util/concurrent/CompletableFuture.html[CompletableFuture] instances is also supported. Note that returning any other type _results in a blocking request_ and is not recommended other than for testing.
---
### Src Main Docs Guide HttpClient ClientFilter (`src/main/docs/guide/httpClient/clientFilter.adoc`)
Often, you need to include the same HTTP headers or URL parameters in a set of requests against a third-party API or when calling another Microservice.
To simplify this, you can define api:http.annotation.ClientFilter[] classes that are applied to all matching HTTP client requests. The details of how filters worked are described in the <<filters, server filter documentation>>. Here we will only show some client filters.
As an example, say you want to build a client to communicate with the https://bintray.com/docs/api/[Bintray REST API]. It would be tedious to specify authentication for every single HTTP call.
To resolve this you can define a filter. The following is an example `BintrayService`:
snippet::io.micronaut.docs.client.ThirdPartyClientFilterSpec[tags="bintrayApiConstants, bintrayService", indent=0]
<1> An link:{micronautreactorapi}/io/micronaut/reactor/http/client/ReactorHttpClient.html[ReactorHttpClient] is injected for the Bintray API
<2> The organization is configurable via configuration
The Bintray API is secured. To authenticate you add an `Authorization` header for every request. You can modify `fetchRepositories` and `fetchPackages` methods to include the necessary HTTP Header for each request, but using a filter is much simpler:
snippet::io.micronaut.docs.client.ThirdPartyClientFilterSpec[tags="bintrayFilter", indent=0]
<1> You can match only a subset of paths with a Client filter.
<2> The `username` and `token` are injected via configuration
<3> The `basicAuth` method includes HTTP Basic credentials
Now when you invoke the `bintrayService.fetchRepositories()` method, the `Authorization` HTTP header is included in the request.
=== Injecting Another Client into a ClientFilter
To create an link:{micronautreactorapi}/io/micronaut/reactor/http/client/ReactorHttpClient.html[ReactorHttpClient], the Micronaut framework needs to resolve all `ClientFilter` beans, which creates a circular dependency when injecting another link:{micronautreactorapi}/io/micronaut/reactor/http/client/ReactorHttpClient.html[ReactorHttpClient] or a `@Client` bean into an instance of a `ClientFilter`.
To resolve this issue, use the api:context.BeanProvider[] interface to inject another link:{micronautreactorapi}/io/micronaut/reactor/http/client/ReactorHttpClient.html[ReactorHttpClient] or a `@Client` bean into an instance of `ClientFilter`.
The following example which implements a filter allowing https://cloud.google.com/run/docs/authenticating/service-to-service[authentication between services on Google Cloud Run] demonstrates how to use api:context.BeanProvider[] to inject another client:
snippet::io.micronaut.docs.client.filter.GoogleAuthFilter[tags="class"]
<1> The api:context.BeanProvider[] interface is used to inject another client, avoiding a circular reference
<2> The `get()` method of the `Provider` interface is used to obtain the client instance.
=== Filter Matching By Annotation
For cases where a filter should be applied to a client regardless of the URL, filters can be matched by the presence of an annotation applied to both the filter and the client. Given the following client:
snippet::io.micronaut.docs.client.filter.BasicAuthClient[tags="class"]
<1> The `@BasicAuth` annotation is applied to the client
The following filter will filter the client requests:
snippet::io.micronaut.docs.client.filter.BasicAuthClientFilter[tags="class"]
<1> The same annotation, `@BasicAuth`, is applied to the filter
<2> Normally the `@Filter` annotation makes filters singletons by default. Because the `@Filter` annotation is not used, the desired scope must be applied
The `@BasicAuth` annotation is just an example and can be replaced with your own.
snippet::io.micronaut.docs.client.filter.BasicAuth[tags="class"]
<1> The only requirement for custom annotations is that the ann:http.annotation.FilterMatcher[] annotation must be present
---
### Src Main Docs Guide HttpClient ClientHttp2 (`src/main/docs/guide/httpClient/clientHttp2.adoc`)
By default, Micronaut's HTTP client is configured to support HTTP 1.1. To enable support for HTTP/2, set the supported HTTP version in configuration:
.Enabling HTTP/2 in Clients
[configuration]
----
micronaut:
http:
client:
http-version: 2.0
----
Or by specifying the HTTP version to use when injecting the client:
.Injecting an HTTP/2 Client
[source,java]
----
@Inject
@Client(httpVersion=HttpVersion.HTTP_2_0)
ReactorHttpClient client;
----
---
### Src Main Docs Guide HttpClient ClientHttp3 (`src/main/docs/guide/httpClient/clientHttp3.adoc`)
Since Micronaut framework 4.x, Micronaut's Netty-based HTTP client can be configured to support HTTP/3.
Instead of the TCP used for HTTP/1.1 and HTTP/2, HTTP/3 runs on UDP. If the client is configured with the special `h3` value for the `alpn-modes` property, the client will automatically use HTTP/3 over UDP instead of HTTP/1.1 or HTTP/2 over TCP. At this time, the client cannot fall back to TCP if the server does not support HTTP/3.
.Enabling HTTP/3 in Clients
[configuration]
----
micronaut:
http:
client:
alpn-modes: [h3]
----
Additionally, the netty HTTP/3 codec needs to be present on the classpath:
dependency:micronaut-http-netty-http3[]
---
### Src Main Docs Guide HttpClient ClientSample (`src/main/docs/guide/httpClient/clientSample.adoc`)
TIP: Read the HTTP Client Guide (https://guides.micronaut.io/micronaut-http-client/guide/index.html[Java],
https://guides.micronaut.io/micronaut-http-client-groovy/guide/index.html[Groovy],
https://guides.micronaut.io/micronaut-http-client-kotlin/guide/index.html[Kotlin]), a step-by-step tutorial, to learn more.
---
### Src Main Docs Guide HttpClient HttpClientImplementations (`src/main/docs/guide/httpClient/httpClientImplementations.adoc`)
There are several implementations of the Micronaut HTTP Client.
---
### Src Main Docs Guide HttpClient LowLevelHttpClient (`src/main/docs/guide/httpClient/lowLevelHttpClient.adoc`)
The api:http.client.HttpClient[] interface forms the basis for the low-level API. This interfaces declares methods to help ease executing HTTP requests and receiving responses.
The majority of the methods in the api:http.client.HttpClient[] interface return Reactive Streams rs:Publisher[] instances, which is not always the most useful interface to work against.
Micronaut's Reactor HTTP Client dependency ships with a sub-interface named link:{micronautreactorapi}/io/micronaut/reactor/http/client/ReactorHttpClient.html[ReactorHttpClient]. It provides a variation of the api:http.client.HttpClient[] interface that returns https://projectreactor.io[Project Reactor] reactor:Flux[] types.
TIP: See the guide for https://guides.micronaut.io/latest/micronaut-http-client.html[Micronaut HTTP Client] to learn more.
The api:http.client.AsyncHttpClient[] exposes `CompletionStage` results for full responses and their converted bodies. Micronaut's clients treat the future returned by `exchange` or `retrieve` as owning the request. Before that future completes, cancelling it or completing it exceptionally, including with `orTimeout`, aborts outstanding work. Cancellation is a best-effort hint: it cannot undo an action the remote server already performed.
A dependent stage created with `thenApply`, `thenCompose`, or another standard stage method does not own the request. Cancelling or timing out only that stage leaves the original request running. Keep the original future and apply a deadline to it when the deadline should abort the request. Do not block on these futures on a client event loop.
This ownership rule applies to client operations, not shared discovery or load-balancer SPI stages. A caller must not cancel or externally complete a shared SPI stage, since other callers may depend on it. Apply a caller-specific timeout to a dependent stage instead.
---
### Src Main Docs Guide HttpClient LowLevelOrHighLevel (`src/main/docs/guide/httpClient/lowLevelOrHighLevel.adoc`)
Since the higher level API is built on the low-level HTTP client, we first introduce the low-level client.
---
### Src Main Docs Guide HttpClient NettyPooling (`src/main/docs/guide/httpClient/nettyPooling.adoc`)
The netty client uses a _connection pool_ to share HTTP connections between requests for greater efficiency. The pools
are scoped by service (the service ID you can configure in the `@Client` annotation), and by authority (host and port),
so if you configure e.g. the maximum number of connections, what is meant is the number of connections _per host_.
You can configure the pool through the `micronaut.http.client.pool.\*` properties or, for specific services, via
`micronaut.http.services.*.pool.*`. I'll only mention the simple property name from here on.
HTTP/1.1 and HTTP/2.0 connections are handled and configured separately, because the latter can handle many concurrent
requests on the same connection. For the purposes of pooling, HTTP/3 connections are treated as HTTP/2.0 connections.
When a request is initiated, it is added to a _pending request queue_. The maximum size of this queue is configured
with `max-pending-acquires`, the default is no limit. If the limit is exceeded, the request is rejected. You can also
configure an `acquire-timeout` (no timeout by default).
Once a request is in the pending request queue, it is dispatched to an existing connection. If all connections are
busy but there are still pending requests, a decision is made on whether to create new connections:
* The maximum number of pending connections (i.e. connections that are still being established) may not exceed
`max-pending-connections` (default: 4).
* For HTTP/1.1, the maximum number of connections may not exceed `max-concurrent-http1-connections` (no limit by
default).
* For HTTP/2.0, the maximum number of connections may not exceed `max-concurrent-http2-connections` (default: 1).
* There will be at most as many pending connections as there are pending requests.
The HTTP version is determined by any existing connections in the pool. If the pool is empty, both limits apply, so by
default only one connection is opened at first. For HTTP/2.0, there is also a
`max-concurrent-requests-per-http2-connection` setting that controls how many requests may run on the same HTTP/2.0
connection simultaneously.
Note that a pending connection is not actually associated to a request until it is fully established. A pending request
may time out, but the connection that was created for it will still enter the pool and may be used by another request.
Once a connection is in the pool, there are multiple ways it may terminate and exit the pool. During a request, the
connection may see a read timeout configured by `read-timeout`. When the connection is idle (no request), a similar
read timeout can be configured by `connection-pool-idle-timeout`. A fixed maximum connection lifetime may be configured
using `connect-ttl`, after which the connection will wind down (no new request will be sent and the connection will
terminate after all requests are done). Certain HTTP errors may also lead to a connection shutdown for safety.
---
### Src Main Docs Guide HttpClient ProxyClient (`src/main/docs/guide/httpClient/proxyClient.adoc`)
A common requirement in Microservice environments is to proxy requests in a Gateway Microservice to other backend Microservices.
The regular api:http.client.HttpClient[] API is designed around simplifying message exchange and is not designed for proxying requests. For this case, use the api:http.client.ProxyHttpClient[], which can be used from an <<filters, HTTP Server Filter>> to proxy requests to backend Microservices.
The following example demonstrates rewriting requests under the URI `/proxy` to the URI `/real` onto the same server (although in a real environment you generally proxy to another server):
snippet::io.micronaut.docs.http.client.proxy.ProxyFilter[tags="imports,class", indent=0, title="Proxy filter that rewrites original requests"]
<1> The filter extends api:http.filter.HttpServerFilter[]
<2> The api:http.client.ProxyHttpClient[] is injected into the constructor.
<3> The `proxy` method proxies the request
<4> The request is mutated to modify the URI and include an additional header
<5> The api:http.uri.UriBuilder[] API rewrites the URI
<6> Additional request and response headers are included
NOTE: The api:http.client.ProxyHttpClient[] API is a low-level API that can be used to build a higher-level abstraction such as an API Gateway.
=== Forwarding Headers
To tell the backend who the original client is, write the forwarding headers of the proxied request with api:http.server.util.ForwardedHeaders[]: `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Prefix` and the RFC 7239 `Forwarded` header.
[source,java]
----
ForwardedHeaders forwardedHeaders = ForwardedHeaders.builder()
.trustedProxy(address -> address.getAddress().isSiteLocalAddress()) // <1>
.build();
forwardedHeaders.write(inboundRequest, proxiedRequest); // <2>
----
<1> Only the forwarding headers sent by a trusted peer are kept. By default no peer is trusted, and the inbound values, which could be forged, are replaced with the values of this hop.
<2> Writes the headers of this hop onto the proxied request.
A trusted proxy is trusted with the values, not with their syntax: an `X-Forwarded-Proto` with a scheme that is not `http`, `https`, `ws` or `wss`, or an `X-Forwarded-Port` with a port that is not a number between 1 and 65535, is dropped, and the value of this hop is sent instead. Such a port is also dropped from an `X-Forwarded-Host`, and such a scheme or port from the `proto` and `host` of a `Forwarded` element. A list of valid values, one per proxy, is kept.
---
### Src Main Docs Guide HttpClient RawClient (`src/main/docs/guide/httpClient/rawClient.adoc`)
The api:http.client.RawHttpClient[] and its asynchronous form api:http.client.AsyncRawHttpClient[] exchange requests and responses as raw bytes: the request body is a api:http.body.CloseableByteBody[] that the client sends as it is, and the response body is handed back unread. A gateway uses them to relay requests to backends.
Each exchange can take api:http.client.RawRequestOptions[], starting from `RawRequestOptions.getDefault()` or `RawRequestOptions.proxy()`. Two of them help a gateway:
* `readIdleTimeout` replaces the read timeout of the client for one exchange: the exchange fails when no response bytes arrive for that long. It can be longer as well as shorter than the `read-timeout` of the client, e.g. for long polls or server-sent events that go quiet for minutes. `null`, the default, keeps the read timeout of the client.
* `returnUnsentBody` hands the request body back when the exchange fails before any connection was opened or acquired, i.e. the body was never read. The exchange fails with an api:http.client.exceptions.UnprocessedRequestException[] whose `takeUnsentBody()` returns the body, so it can be sent to another server. Whoever takes the body owns it and must send or close it. A request that was redirected does not hand its body back: it was sent to the server that redirected it. Only the asynchronous raw client of the Netty client hands a body back; it is closed otherwise.
The following example sends a request to a primary server and, if no connection to it could be opened, sends the same body to a fallback server:
snippet::io.micronaut.docs.http.client.raw.RawRetry[tags="imports,class", indent=0, title="Retrying a raw exchange with the unsent body"]
<1> The exchange waits up to five minutes for response bytes, whatever the read timeout of the client
<2> A body that was never sent is handed back instead of being closed
<3> Take the body from the exception; it is empty if the body was read or taken already
<4> Send the body to the fallback server, which now owns it
The future returned by `AsyncRawHttpClient.exchange` owns the pending exchange. Cancelling it or completing it exceptionally (including `orTimeout`) aborts outstanding work and releases the request body. A response arriving after cancellation or timeout is closed. Cancelling or timing out a dependent stage leaves the original exchange running.
Once the response has arrived, the caller owns its body. Close the api:http.ByteBodyHttpResponse[] when finished, including when abandoning it after a later processing timeout. Cancelling an already completed exchange future does not release an active response body.
---
### Src Main Docs Guide HttpServer ApiVersioning (`src/main/docs/guide/httpServer/apiVersioning.adoc`)
Since 1.1.x, the Micronaut framework supports API versioning via a dedicated ann:core.version.annotation.Version[] annotation.
The following example demonstrates how to version an API:
snippet::io.micronaut.docs.web.router.version.VersionedController[tags="imports,clazz", indent=0, title="Versioning an API"]
<1> The `helloV1` method is declared as version `1`
<2> The `helloV2` method is declared as version `2`
Then enable versioning by setting `micronaut.router.versioning.enabled` to `true` in your configuration file (e.g `application.yml`):
.Enabling Versioning
[configuration]
----
micronaut:
router:
versioning:
enabled: true
----
By default, the Micronaut framework has two strategies for resolving the version based on an HTTP header named `X-API-VERSION` or a request parameter named `api-version`, however this is configurable. A full configuration example can be seen below:
.Configuring Versioning
[configuration]
----
micronaut:
router:
versioning:
enabled: true
parameter:
enabled: false
names: 'v,api-version'
header:
enabled: true
names:
- 'X-API-VERSION'
- 'Accept-Version'
----
- This example enables versioning
- `parameter.enabled` enables or disables parameter-based versioning
- `parameter.names` specifies the parameter names as a comma-separated list
- `header.enabled` enables or disables header-based versioning
- `header.names` specifies the header names as a list
If this is not enough you can also implement the api:web.router.version.resolution.RequestVersionResolver[] interface which receives the api:http.HttpRequest[] and can implement any strategy you choose.
=== Default Version
It is possible to supply a default version through configuration.
.Configuring Default Version
[configuration]
----
micronaut:
router:
versioning:
enabled: true
default-version: 3
----
- This example enables versioning and sets the default version
A route is *not* matched if the following conditions are met:
* The default version is configured
* No version is found in the request
* The route defines a version
* The route version does not match the default version
If the incoming request specifies a version, the default version has no effect.
=== Versioning Client Requests
Micronaut's <<clientAnnotation, Declarative HTTP client>> also supports automatic versioning of outgoing requests via the ann:core.version.annotation.Version[] annotation.
By default, if you annotate a client interface with ann:core.version.annotation.Version[], the value supplied to the annotation is included using the `X-API-VERSION` header.
For example:
snippet::io.micronaut.docs.client.versioning.HelloClient[tags="imports,clazz"]
<1> The ann:core.version.annotation.Version[] annotation can be used at the type level to specify the version to use for all methods
<2> When defined at the method level it is used only for that method
The default behaviour for how the version is sent for each call can be configured with api:http.client.interceptor.configuration.DefaultClientVersioningConfiguration[]:
include::{includedir}configurationProperties/io.micronaut.http.client.interceptor.configuration.DefaultClientVersioningConfiguration.adoc[]
For example to use `Accept-Version` as the header name:
.Configuring Client Versioning
[configuration]
----
micronaut:
http:
client:
versioning:
default:
headers:
- 'Accept-Version'
- 'X-API-VERSION'
----
The `default` key refers to the default configuration. You can specify client-specific configuration by using the value passed to `@Client` (typically the service ID). For example:
.Configuring Versioning
[configuration]
----
micronaut:
http:
client:
versioning:
greeting-service:
headers:
- 'Accept-Version'
- 'X-API-VERSION'
----
The above uses a key named `greeting-service` which can be used to configure a client annotated with `@Client('greeting-service')`.
---
### Src Main Docs Guide HttpServer AsyncRequestBody (`src/main/docs/guide/httpServer/asyncRequestBody.adoc`)
A method argument of type api:http.body.AsyncRequestBody[] gives the body of the request without reading it. The method decides how to read it: decoded to a type, as text or bytes, element by element, written to a file, or taken as is. Each read returns a `CompletionStage`, so the method can return a stage and never block.
NOTE: api:http.body.AsyncRequestBody[] and api:http.body.BodyElements[] are experimental. Their API may change in a future release.
snippet::io.micronaut.docs.server.asyncbody.PeopleController[tags="imports,class,body,store,endclass", title="Decoding the Body"]
<1> The method declares an api:http.body.AsyncRequestBody[] argument, and returns a `CompletionStage`
<2> `body` decodes the whole body to a type, like a api:http.annotation.Body[] argument
<3> The decoded body is stored, and the stage completes with the response
`body` decodes the same way as a api:http.annotation.Body[] argument: with the same message body readers and the same errors. A malformed JSON body is answered with `400 Bad Request`, and a body larger than `micronaut.server.max-request-buffer-size` is answered with `413 Request Entity Too Large`. The stage returned by the method fails with the exception, which the error handlers answer like they answer it for a api:http.annotation.Body[] argument.
== Reading the Body
The other ways to read the body are:
* `text()` reads the body as a string, in the charset of the request, up to `micronaut.server.max-request-buffer-size` bytes. `text(maximumBytes)` sets a lower limit, and `text(maximumBytes, charset)` reads the body in the given charset instead.
* `bytes(maximumBytes)` reads the body into a `byte[]`, up to `maximumBytes` and never more than `micronaut.server.max-request-buffer-size` bytes.
* `transferTo(path)` writes the body to a new file, without holding it in memory, so `micronaut.server.max-request-buffer-size` does not limit it.
* `takeBody()` hands the body over as a api:http.body.CloseableByteBody[], unread. The method must then consume or close it, e.g. by sending it on with an HTTP client.
* `discardBody()` discards the body without reading it.
* `form()` and `parts()` read a form, see <<formUploads, Form Data and File Uploads>>.
`body`, `text` and `bytes` hold the whole body in memory, so `micronaut.server.max-request-buffer-size` always limits them, however the body is chunked and whenever it arrives. The `maximumBytes` of `text` and `bytes` lowers that limit and never raises it: the body is read up to the smaller of the two, and a larger body is answered with `413 Request Entity Too Large`. To read a body larger than the buffer limit, read it with `elements`, which limits each element, write it to a file with `transferTo`, or take it with `takeBody()`.
snippet::io.micronaut.docs.server.asyncbody.PeopleController[tags="text", indent=0, title="Reading the Body as Text"]
<1> `text` completes with the whole body as a string
`hasBody()` and `expectedBodySize()` tell whether the request has a body and how large the client said it is, without reading it.
WARNING: The body can be read once. The first read owns the body, and a second read, even with another method, throws an `IllegalStateException`. A body that is not read is discarded when the request ends. To read the body more than once, read a copy of it, see <<asyncRequestBodyCopies, Copies of the Body>>.
What a read left open is released when the method completes, before the response is written or the filter chain continues: a read the method did not wait for, e.g. `elements` or `transferTo` whose stage it did not return, is aborted and its buffers and files are released, so the work on the body must be part of the stage the method returns. A controller method that answers with a streamed response, e.g. a `Publisher` of many items, keeps the body until the stream ends: when it completes, fails or is cancelled, e.g. because the client disconnected. The stream can be made of the reads of the body, e.g. one item for each element read with `elements`.
`body` and `elements` decode a whole value in memory, so they refuse the types that would read it in some other way: a `Publisher`, a `CompletableFuture` or another asynchronous type, and `InputStream` throw an `IllegalArgumentException`. To stream the body, read it element by element, or take it with `takeBody()`.
== Reading the Elements of a JSON Array or Stream
`elements` decodes a JSON array (`application/json`) or a JSON stream (`application/x-json-stream`, one value per line) one element at a time:
snippet::io.micronaut.docs.server.asyncbody.PeopleController[tags="elements", indent=0, title="Reading the Elements"]
<1> The route accepts a JSON array and a JSON stream
<2> `elements` returns the api:http.body.BodyElements[] of the body, nothing is read yet
<3> `forEach` reads an element, and reads the next one once the stage the consumer returned completes
<4> The stage of `forEach` completes once every element was consumed
Only what the method asks for is read: the next element is received once the consumer is done with the previous one, so a slow consumer slows down the client instead of filling the memory of the server. `next()` reads a single element instead, and completes with an empty `Optional` at the end of the body. The elements that are not read are discarded.
The limit `micronaut.server.max-request-buffer-size` applies to each element, not to the whole body: an element larger than the limit fails with `413`, while the body as a whole is only limited by `micronaut.server.max-request-size`. This holds even for a limit smaller than what the server receives before the method reads the elements, e.g. with the request headers.
A request of another media type fails the first read of the elements with `415 Unsupported Media Type`.
IMPORTANT: The readers that decode JSON one element at a time are part of `micronaut-http-netty`, which the Netty server includes. A server that is not the Netty server, e.g. a servlet container, needs `micronaut-http-netty` on its runtime classpath to read elements. Without it, the first read fails with an `UnsupportedOperationException`.
TIP: The stages complete on a thread chosen by the server, usually an event loop thread, so a consumer must not block. Return the stage of an asynchronous call instead, e.g. of a reactive database driver.
[[asyncRequestBodyCopies]]
== Copies of the Body
`copy()` returns another api:http.body.AsyncRequestBody[] over a copy of the bytes of the body. Reading the copy does not consume the body: the route, or another reader, can still read it in any way. The copy has its own single read, and so does the body it was copied from.
The bytes the copy reads are kept for the body until it is read, so a copy is held to `micronaut.server.max-request-buffer-size` as a whole, like a api:http.annotation.Body[] argument of a filter, and a larger body is answered with `413 Request Entity Too Large`. This holds for the streaming reads of a copy too, `elements`, `parts` and `transferTo`: a copy is for looking at the body, not for streaming a large upload. `form()` of a copy is the one form of the request, which it reads from a split of the bytes, so the body stays readable in any way and the form is held to the limit as a whole, and `parts()` of a copy decodes the form a second time, from a split of the bytes.
== In Filters
The request filter methods of a api:http.annotation.ServerFilter[] can declare an api:http.body.AsyncRequestBody[] argument too, and read the body before the route runs. The filter method returns the stage of the read, and the request continues once it completes.
The arguments of a filter look at the body, the reads of an api:http.body.AsyncRequestBody[] consume it, and the reads of a copy look at it:
[cols="2,3,3"]
|===
|In a filter |Reads |The route afterwards
|A api:http.annotation.Body[] argument
|A buffered copy of the bytes
|Reads the body in any way
|A api:http.form.FormData[], api:http.form.FileUpload[] or api:http.annotation.Part[] argument
|The one form of the request, shared by the filters and the route
|Reads the form in any way
|`body`, `text`, `bytes`, `elements`, `transferTo`, `form`, `parts`, `takeBody` or `discardBody` of an api:http.body.AsyncRequestBody[], the parts of a api:http.form.FormParts[] argument, or a api:http.form.FormPart[] argument
|The body itself: what the filter does not read is discarded
|Fails when it reads the body, with a api:http.annotation.Body[], api:http.form.FormData[], api:http.form.FileUpload[] or api:http.annotation.Part[] argument or an api:http.body.AsyncRequestBody[], with `500 Internal Server Error`. A route that does not read the body answers.
|The same reads of `copy()`
|A copy of the bytes, or the shared form for `copy().form()`
|Reads the body in any way
|===
The reads of an api:http.body.AsyncRequestBody[] in a filter are for a filter that consumes the body itself, e.g. to forward it with `takeBody()`, or to reject the request. When the route reads the body after them, the `IllegalStateException` of the failure names the read of the filter, e.g. `The body of the request was already read with text() of an AsyncRequestBody, e.g. by a filter, which consumes it: read it with copy() in the filter to leave it for the route, or bind it with @Body`.
To look at the body and leave it for the route, the filter reads a copy:
snippet::io.micronaut.docs.server.asyncbody.SpamFilter[tags="imports,class", title="Reading a Copy of the Body in a Filter"]
<1> The request filter method declares an api:http.body.AsyncRequestBody[] argument, and returns a `CompletionStage` of a nullable response
<2> `copy()` reads a copy of the body, and leaves the body for the route
<3> The filter answers the request itself when the body contains spam
<4> Otherwise the request continues to the route
snippet::io.micronaut.docs.server.asyncbody.MessageController[tags="imports,class", title="The Route Reads the Body"]
<1> The route reads the same body with a api:http.annotation.Body[] argument
A filter that reads the body or a copy of it buffers the body before the route runs, and the body is then held to `micronaut.server.max-request-buffer-size` as a whole. For a form, a api:http.form.FormData[] argument or `copy().form()` is the shared form, which the form arguments of the route do not decode a second time. After a api:http.form.FormData[] argument of a filter the route reads the form, not the bytes of the body. After `copy().form()` the route can still read the body in any way, e.g. with `text()` or a api:http.annotation.Body[] argument, since the copy keeps the bytes for it. `copy().parts()` decodes the form a second time, so it is only for a filter that must stream through the parts and still leave the body for the route. See <<formUploads, Form Data and File Uploads>>.
`form(FormOptions.defaults().withRawValues())` reads the fields of an `application/x-www-form-urlencoded` form with their values as they appear in the body, not percent-decoded: `a%2Fb`, a `+` that is not a space, and the percent-encoded bytes of a non-ASCII value, e.g. for a framework that must not decode a field. The names are decoded, and the form has the limits and the validation of a decoded form. It is not the shared form of the request: the form arguments do not share it, and it does not reuse a form they read, as it reads the bytes of the body, which it then needs: after another read consumed them, or when a filter replaced the body with an object, it fails. Of a copy it reads a split of the bytes and leaves the body for the route. A `multipart/form-data` form has no percent-encoding, so it is read like `form()`. The raw values of the path variables are read in a similar way, with `raw()` of api:http.PathVariables[].
A filter that continues with a mutable request whose body it cleared, with `request.body(null)`, leaves no body: `hasBody()` is `false` and `text()` is empty. A body the filter set to an object is read with `body`, which converts it like a api:http.annotation.Body[] argument.
---
### Src Main Docs Guide HttpServer Binding (`src/main/docs/guide/httpServer/binding.adoc`)
The examples in the previous section demonstrate how the Micronaut framework lets you bind method parameters from URI path variables. This section shows how to bind arguments from other parts of the request.
== Binding Annotations
All binding annotations support customization of the name of the variable being bound from with their `name` member.
The following table summarizes the annotations and their purpose, and provides examples:
.Parameter Binding Annotations
|===
|Annotation|Description |Example
|link:{api}/io/micronaut/http/annotation/Body.html[@Body]
|Binds from the body of the request
|`@Body String body`
|link:{api}/io/micronaut/http/annotation/CookieValue.html[@CookieValue]
|Binds a parameter from a cookie
|`@CookieValue String myCookie`
|link:{api}/io/micronaut/http/annotation/Header.html[@Header]
|Binds a parameter from an HTTP header
|`@Header String requestId`
|link:{api}/io/micronaut/http/annotation/QueryValue.html[@QueryValue]
|Binds from a request query parameter
|`@QueryValue String myParam`
|link:{api}/io/micronaut/http/annotation/Part.html[@Part]
|Binds from a part of a multipart request
|`@Part CompletedFileUpload file`
|link:{api}/io/micronaut/http/annotation/RequestAttribute.html[@RequestAttribute]
|Binds from an attribute of the request. Attributes are typically created in filters
|`@RequestAttribute String myAttribute`
|link:{api}/io/micronaut/http/annotation/PathVariable.html[@PathVariable]
|Binds from the path of the request
|`@PathVariable String id`
|link:{api}/io/micronaut/http/annotation/RequestBean.html[@RequestBean]
|Binds any Bindable value to single Bean object
|`@RequestBean MyBean bean`
|===
The method parameter name is used when a value is not specified in a binding annotation. In other words the following two methods are equivalent and both bind from a cookie named `myCookie`:
snippet::io.micronaut.docs.server.binding.BindingController[tags="cookie1,cookie2",indent=0]
Because hyphens are not allowed in variable names, it may be necessary to set the name in the annotation. The following definitions are equivalent:
snippet::io.micronaut.docs.server.binding.BindingController[tags="header1,header2",indent=0]
== Stream Support
The Micronaut framework also supports binding the body to an `InputStream`. If the method is reading the stream, the method execution must be offloaded to another thread pool to avoid blocking the event loop.
snippet::io.micronaut.docs.http.server.stream.StreamController[tags="read", indent=0, title="Performing Blocking I/O With InputStream"]
<1> The controller method is executed on the IO thread pool
<2> The body is passed to the method as an input stream
<3> The stream is read
== Binding from Multiple Query values
Instead of binding from a single section of the request, it may be desirable to bind all query values for example to a POJO. This can be achieved by using the exploded operator (`?pojo*`) in the URI template. For example:
snippet::io.micronaut.docs.server.binding.BookmarkController[tags="imports,class", indent=0, title="Binding Request parameters to POJO"]
== Binding from Multiple Bindable values
Instead of binding just query values, it is also possible to bind any Bindable value to a POJO (e.g. to bind `HttpRequest`, `@PathVariable`, `@QueryValue` and `@Header` to a single POJO). This can be achieved with the `@RequestBean` annotation and a custom Bean class with fields with Bindable annotations, or fields that can be bound by type (e.g. `HttpRequest`, `BasicAuth`, `Authentication`, etc).
For example:
snippet::io.micronaut.docs.server.binding.MovieTicketController[tags="class", indent=0, title="Binding Bindable values to POJO"]
which uses this bean class:
snippet::io.micronaut.docs.server.binding.MovieTicketBean[tags="class", indent=0, title="Bean definition"]
The bean class has to be introspected with `@Introspected`. It can be one of:
. Mutable Bean class with setters and getters
. Immutable Bean class with getters and an all-argument constructor (or `@Creator` annotation on a constructor or static method). Arguments of the constructor must match field names so the object can be instantiated without reflection.
WARNING: Since Java does not retain argument names in bytecode, you must compile code with `-parameters` to use an immutable bean class from another jar. Another option is to extend Bean class in your source.
== Bindable Types
Generally any type that can be converted from a String representation to a Java type via the link:{api}/io/micronaut/core/convert/ConversionService.html[ConversionService] API can be bound to.
This includes most common Java types. However, you can simply add additional link:{api}/io/micronaut/core/convert/TypeConverter.html[TypeConverter] either by defining it as a bean or by registering it in a api:core.convert.TypeConverterRegistrar[] via the service loader.
The handling of nullability deserves special mention. Consider for example the following example:
snippet::io.micronaut.docs.server.binding.BindingController[tags="header2",indent=0]
In this case, if the HTTP header `Content-Type` is not present in the request, the route is considered invalid, since it cannot be satisfied, and an HTTP 400 `BAD REQUEST` is returned.
To make the `Content-Type` header optional, you can instead write:
snippet::io.micronaut.docs.server.binding.BindingController[tags="header3",indent=0]
A `null` string is passed if the header is absent from the request.
NOTE: `java.util.Optional` can also be used, but that is discouraged for method parameters.
Additionally, any `DateTime` that conforms to link:{jdkapi}/java.base/java/time/format/DateTimeFormatter.html#RFC_1123_DATE_TIME[RFC-1123] can be bound to a parameter. Alternatively the format can be customized with the link:{api}/io/micronaut/core/convert/format/Format.html[Format] annotation:
snippet::io.micronaut.docs.server.binding.BindingController[tags="format1,format2",indent=0]
== Type-Based Binding Parameters
Some parameters are recognized by their type instead of their annotation. The following table summarizes the parameter types, their purpose, and provides an example:
|===
|Type |Description |Example
|api:http.BasicAuth[]
|Allows binding of basic authorization credentials
|`BasicAuth basicAuth`
|===
== Variable resolution
The Micronaut framework tries to populate method arguments in the following order:
. URI variables like `/{id}`.
. From query parameters if the request is a `GET` request (e.g. `?foo=bar`).
. If there is a `@Body` and request allows the body, bind the body to it.
. If the request can have a body and no `@Body` is defined then try to parse the body (either JSON or form data) and bind the method arguments from the body (see the example).
. Finally, if the method arguments cannot be populated return `400 BAD REQUEST`.
snippet::io.micronaut.docs.server.binding.PointController[tags="class", indent=0, title="Binding Method Arguments From Body with no `@Body`"]
<1> JSON request body binds to method controller arguments, e.g. '{"x":10,"y":20}' (with "application/json")
<2> Form data also works, e.g. 'x=10&y=20' (with "application/x-www-form-urlencoded")
---
### Src Main Docs Guide HttpServer ByteBody (`src/main/docs/guide/httpServer/byteBody.adoc`)
Micronaut HTTP server version 4.5.0 introduced a new, more advanced API to access the bytes of an incoming request.
When an api:io.micronaut.http.HttpRequest[] implements api:io.micronaut.http.ServerHttpRequest[], the new `byteBody()`
method returns a api:io.micronaut.http.body.ByteBody[] with buffered, reactive and blocking APIs.
When an HTTP request comes in, it starts out with an unparsed and unclaimed stream of bytes as its `byteBody()`. After
all request filters have run, typically an argument binder matching the `@Body` parameter of the controller will
"claim" the `byteBody()` and e.g. parse the JSON. Finally, the body is closed at the end of the request lifecycle,
discarding any data if it has not been claimed by the argument binder.
NOTE: The api:http.filter.FilterBodyParser[] API allows you to get a Map representation of a request's body whose content type is either application/x-www-form-urlencoded or application/json.
---
### Src Main Docs Guide HttpServer ClientIpAddress (`src/main/docs/guide/httpServer/clientIpAddress.adoc`)
You may need to resolve the originating IP address of an HTTP Request. The Micronaut framework includes an implementation of api:http.server.util.HttpClientAddressResolver[].
The default implementation resolves the client address in the following places in order:
. The configured header
. The `Forwarded` header
. The `X-Forwarded-For` header
. The remote address on the request
The first priority header name can be configured with `micronaut.server.client-address-header`.
---
### Src Main Docs Guide HttpServer ConsumesAnnotation (`src/main/docs/guide/httpServer/consumesAnnotation.adoc`)
A Micronaut controller action consumes `application/json` by default. Consuming other content types is supported with the `@Consumes` annotation, or the `consumes` member of any HTTP method annotation.
snippet::io.micronaut.docs.server.consumes.ConsumesController[tags="imports,clazz", indent=0]
<1> By default, a controller action consumes request with `Content-Type` of type `application/json`.
<2> The `@Consumes` annotation takes a `String[]` of supported media types for an incoming request.
<3> Content types can also be specified with the `consumes` member of the method annotation.
=== Customizing Processed Content Types
Normally JSON parsing only happens if the content type is `application/json`. The other api:io.micronaut.http.codec.MediaTypeCodec[] classes behave similarly in that they have predefined content types they can process. To extend the list of media types that a given codec processes, provide configuration that will be stored in api:io.micronaut.http.codec.CodecConfiguration[]:
[configuration]
----
micronaut:
codec:
json:
additional-types:
- text/javascript
- ...
----
The currently supported configuration prefixes are `json`, `json-stream`, `text`, and `text-stream`.
---
### Src Main Docs Guide HttpServer ContentDispositionAnnotation (`src/main/docs/guide/httpServer/contentDispositionAnnotation.adoc`)
To send a `Content-Disposition` response header, use the `@ContentDisposition` annotation. This is independent of the return type of the method, so it works with a plain object, a reactive type, or anything else the route returns.
snippet::io.micronaut.docs.server.response.ContentDispositionController[tags="attachment", indent=0]
The example above results in a response with the header `Content-Disposition: attachment; filename="report.csv"; filename*=utf-8''report.csv`, prompting the browser to download the response as a file.
To have the browser display the response instead of downloading it, set `type` to `ContentDisposition.Type.INLINE`:
snippet::io.micronaut.docs.server.response.ContentDispositionController[tags="inline", indent=0]
---
### Src Main Docs Guide HttpServer CustomArgumentBinding (`src/main/docs/guide/httpServer/customArgumentBinding.adoc`)
The Micronaut framework uses an api:core.bind.ArgumentBinderRegistry[ArgumentBinderRegistry] to look up api:core.bind.ArgumentBinder[ArgumentBinder] beans
capable of binding to the arguments in controller methods. The default implementation looks for an annotation on the argument that is meta-annotated with ann:core.bind.annotation.Bindable[]. If one exists the argument binder registry searches for an argument binder that supports that annotation.
If no fitting annotation is found, the Micronaut framework tries to find an argument binder that supports the argument type.
An argument binder returns a link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html[ArgumentBinder.BindingResult]. The binding result gives the Micronaut framework more information than just the value. Binding results are either satisfied or unsatisfied, and either empty or not empty. If an argument binder returns an unsatisfied result, the binder may be called again at different times in request processing. Argument binders are initially called before the body is read and before any filters are executed. If a binder relies on any of that data, and it is not present, return a link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html#UNSATISFIED[ArgumentBinder.BindingResult#UNSATISFIED] result. Returning an link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html#EMPTY[ArgumentBinder.BindingResult#EMPTY] or satisfied result will be the final result and the binder will not be called again for that request.
NOTE: At the end of processing if the result is still link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html#UNSATISFIED[ArgumentBinder.BindingResult#UNSATISFIED], it is considered link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html#EMPTY[ArgumentBinder.BindingResult#EMPTY].
Key interfaces are:
=== AnnotatedRequestArgumentBinder
Argument binders that bind based on the presence of an annotation must implement api:http.bind.binders.AnnotatedRequestArgumentBinder[AnnotatedRequestArgumentBinder], and can be used by creating an annotation that is annotated with api:io.micronaut.core.bind.annotation.Bindable[@Bindable]. For example:
snippet::io.micronaut.docs.http.server.bind.annotation.ShoppingCart[tags="class", indent=0, title="An example of a binding annotation"]
<1> The binding annotation must itself be annotated as api:io.micronaut.core.bind.annotation.Bindable[@Bindable]
snippet::io.micronaut.docs.http.server.bind.annotation.ShoppingCartRequestArgumentBinder[tags="class", indent=0, title="Example of annotated data binding"]
<1> The custom argument binder must implement api:io.micronaut.http.bind.binders.AnnotatedRequestArgumentBinder[AnnotatedRequestArgumentBinder], including both the annotation type to trigger the binder (in this case, `MyBindingAnnotation`) and the type of the argument expected (in this case, `Object`)
<2> Override the `bind` method with the custom argument binding logic - in this case, we resolve the name of the annotated argument, extract a value from a cookie with that same name, and convert that value to the argument type
TIP: It is common to use api:io.micronaut.core.convert.ConversionService[ConversionService] to convert the data to the type of the argument.
Once the binder is created, we can annotate an argument in our controller method which will be bound using the custom logic we've specified.
snippet::io.micronaut.docs.http.server.bind.annotation.ShoppingCartController[tags="method", indent=0, title="A controller operation with this annotated binding"]
<1> The parameter is bound with the binder associated with `MyBindingAnnotation`. This takes precedence over a type-based binder, if applicable.
=== TypedRequestArgumentBinder
Argument binders that bind based on the type of the argument must implement api:http.bind.binders.TypedRequestArgumentBinder[TypedRequestArgumentBinder]. For example, given this class:
snippet::io.micronaut.docs.http.server.bind.type.ShoppingCart[tags="class", indent=0, title="Example of POJO"]
We can define a `TypedRequestArgumentBinder` for this class, as seen below:
snippet::io.micronaut.docs.http.server.bind.type.ShoppingCartRequestArgumentBinder[tags="class", indent=0, title="Example of typed data binding"]
<1> Override the `bind` method with the data type to bind, in this example the `ShoppingCart` type
<2> After retrieving the data (in this case, by deserializing JSON text from a cookie), return as a link:{api}/io/micronaut/core/bind/ArgumentBinder.BindingResult.html[ArgumentBinder.BindingResult]
<3> Also override the `argumentType` method, which is used by the ArgumentBinderRegistry.
Once the binder is created, it is used for any controller argument of the associated type:
snippet::io.micronaut.docs.http.server.bind.type.ShoppingCartController[tags="method", indent=0, title="A controller operation with this typed binding"]
<1> The parameter is bound using the custom logic defined for this type in our `TypedRequestArgumentBinder`
---
### Src Main Docs Guide HttpServer Datavalidation (`src/main/docs/guide/httpServer/datavalidation.adoc`)
It is easy to validate incoming data with Micronaut controllers using <<validation, Validation Advice>>.
The Micronaut framework provides native support for the `jakarta.validation` annotations with the `micronaut-validation` dependency:
dependency:micronaut-validation-processor[groupId="io.micronaut.validation",scope="annotationProcessor"]
dependency:micronaut-validation[groupId="io.micronaut.validation"]
Or full https://beanvalidation.org/2.0/spec/[JSR 380] compliance with the `micronaut-hibernate-validator` dependency:
dependency:micronaut-hibernate-validator[groupId="io.micronaut.beanvalidation"]
We can validate parameters using `jakarta.validation` annotations and the api:validation.Validated[] annotation at the class level.
snippet::io.micronaut.docs.datavalidation.params.EmailController[tags="imports,clazz", indent=0,title="Example"]
<1> Annotate controller with api:validation.Validated[]
<2> `subject` and `recipient` cannot be blank.
If a validation error occurs a `jakarta.validation.ConstraintViolationException` is thrown. By default, the integrated `io.micronaut.validation.exception.ConstraintExceptionHandler` handles the exception, leading to a behaviour as shown in the following test:
snippet::io.micronaut.docs.datavalidation.params.EmailControllerSpec[tags="paramsvalidated",indent=0,title="Example Test"]
To use your own `ExceptionHandler` to handle the constraint exceptions, annotate it with `@Replaces(ConstraintExceptionHandler.class)`
Often you may want to use POJOs as controller method parameters.
snippet::io.micronaut.docs.datavalidation.pogo.Email[tags="clazz", indent=0]
<1> You can use `jakarta.validation` annotations in your POJOs.
Annotate your controller with api:validation.Validated[], and annotate the binding POJO with `@Valid`.
snippet::io.micronaut.docs.datavalidation.pogo.EmailController[tags="imports,clazz", indent=0,title="Example"]
<1> Annotate the controller with api:validation.Validated[]
<2> Annotate the POJO to validate with `@Valid`
Validation of POJOs is shown in the following test:
snippet::io.micronaut.docs.datavalidation.pogo.EmailControllerSpec[tags="pojovalidated",indent=0]
NOTE: Bean injection is supported in custom constraints with the Hibernate Validator configuration.
---
### Src Main Docs Guide HttpServer ErrorHandling (`src/main/docs/guide/httpServer/errorHandling.adoc`)
Sometimes with distributed applications, bad things happen. Having a good way to handle errors is important.
---
### Src Main Docs Guide HttpServer Filters (`src/main/docs/guide/httpServer/filters.adoc`)
The Micronaut HTTP server supports applying filters to request/response processing in a similar (but reactive) way to Servlet filters in traditional Java applications.
Filters support the following use cases:
* Decoration of the incoming api:http.HttpRequest[]
* Modification of the outgoing api:http.HttpResponse[]
* Implementation of cross-cutting concerns such as security, tracing, etc.
There are two ways to implement a filter:
- <<httpServerFilter, Annotating class with api:http.annotation.Filter[] and implementing api:http.filter.HttpServerFilter[] or api:http.filter.HttpClientFilter[]>>.
- By using <<filtermethods,Filter methods>>.
NOTE: We recommend Micronaut developers use <<filtermethods,Filter methods>> introduced in Micronaut Framework 4.0 to implement filters.
---
### Src Main Docs Guide HttpServer Form (`src/main/docs/guide/httpServer/form.adoc`)
The Micronaut HTTP server includes support for forms, both using `multipart/form-data` and using `application/x-www-form-urlencoded` format.
---
### Src Main Docs Guide HttpServer FormData (`src/main/docs/guide/httpServer/formData.adoc`)
To make data binding model customizations consistent between form data and JSON, the Micronaut framework uses Jackson to implement binding data from form submissions.
The advantage of this approach is that the same Jackson annotations used for customizing JSON binding can be used for form submissions.
IMPORTANT: Form URL encoded content type and Jackson annotations are not supported by the <<httpClient, Micronaut HTTP Client>>.
In practice this means that to bind regular form data, the only change required to the <> is updating the api:http.MediaType[] consumed:
snippet::io.micronaut.docs.server.form.PersonController[tags="class,formbinding,endclass", title="Binding Form Data to POJOs"]
TIP: To avoid denial-of-service attacks, collection types and arrays created during binding are limited by the setting `jackson.arraySizeThreshold` in your configuration file (e.g `application.yml`)
Alternatively, instead of using a POJO you can bind form data directly to method parameters (which works with JSON too!):
snippet::io.micronaut.docs.server.form.PersonController[tags="class,formsaveWithArgs,endclass", title="Binding Form Data to Parameters"]
As you can see from the example above, this approach lets you use features such as support for `@Nullable` or link:{jdkapi}/java.base/java/util/Optional.html[Optional] types and restrict the parameters to be bound. When using POJOs you must be careful to use Jackson annotations to exclude properties that should not be bound.
TIP: To read the fields of a form without binding them to a POJO, e.g. when the names are not known in advance, declare a api:http.form.FormData[] argument, see <<formUploads, Form Data and File Uploads>>.
---
### Src Main Docs Guide HttpServer FormUploads (`src/main/docs/guide/httpServer/formUploads.adoc`)
The api:http.form.FormData[], api:http.form.FileUpload[], api:http.form.FormPart[] and api:http.form.FormParts[] types read a submitted form, `application/x-www-form-urlencoded` or `multipart/form-data`, without a `Publisher`. Every operation that reads content returns a `CompletionStage`, so a method can return a stage and never block.
NOTE: These types are experimental. Their API may change in a future release.
== The Whole Form
A method argument of type api:http.form.FormData[] receives the whole form, read before the method runs:
snippet::io.micronaut.docs.server.formdata.ProfileController[tags="imports,class,formData,endclass", title="Reading the Whole Form"]
<1> The method declares a api:http.form.FormData[] argument
<2> `getString` reads a required text field
<3> `getInt` with a default value reads an optional text field
<4> `getFile` returns the uploaded file of a field as a api:http.form.FileUpload[]
<5> `bytes` reads the content of the file, and the method returns the stage
Text fields convert with accessors like the ones of <>: `getString`, `getInt` and the other types for a required field, the same with a default value for an optional one, `findString`, `findInt` and the other `find` methods, and `get(name, type)` for any other type. `getValues` returns every value of a field, e.g. of checkboxes, and `get` with an api:core.type.Argument[] of a collection, e.g. `Argument.listOf(Integer.class)`, converts every value. `getFile`, `findFile` and `getFiles` return the uploaded files of a field.
A required field that is missing, or a value that does not convert, is answered with `400 Bad Request`.
The uploaded files are stored in memory or on disk, depending on the `micronaut.server.multipart` configuration, see <>. A file larger than `micronaut.server.multipart.max-file-size` is answered with `413 Request Entity Too Large`.
== Binding Files and Fields
A method can also declare the files and text fields it needs as arguments, bound by name or with the api:http.annotation.Part[] annotation:
snippet::io.micronaut.docs.server.formdata.ProfileController[tags="arguments", indent=0, title="Binding Files and Fields"]
<1> A text field, bound by name
<2> A api:http.form.FileUpload[] bound to the `avatar` field with api:http.annotation.Part[]
<3> An optional file: `null` when the field was not sent. An `Optional<FileUpload>` works too
<4> Every file sent in the `documents` field
<5> `transferTo` writes the file to a new file, and the method returns the stage
The arguments share one form: a api:http.form.FormData[] argument, the api:http.form.FileUpload[] arguments and the text field arguments of a method are all read from the same form, and `form.getFile("avatar")` returns the same api:http.form.FileUpload[] as an `avatar` argument. A required file or field that is missing is answered with `400 Bad Request`.
== Consuming an Upload
The content of a api:http.form.FileUpload[] can be consumed once, with one of:
* `bytes(maximumBytes)`, which reads the content into memory
* `text(maximumBytes)`, which reads the content into memory as text, in the charset of the part's `Content-Type`, else the charset of the request, else the default charset of the server. `text(maximumBytes, charset)` reads it in the given charset instead.
* `transferTo(path)`, which writes the content to a new file. The file must not exist, and its directory must exist. Once written, the file belongs to the application.
* `transferTo(outputStream)`, which writes the content to a stream
* `takeBody()`, which hands the content over as a api:http.body.CloseableByteBody[], which the caller must then consume or close
None of them blocks: the reads return a `CompletionStage`, whether the content is in memory, on disk or still arriving, and content stored on disk is read on the I/O executor. A second consumption throws an `IllegalStateException`. The name, file name, content type and size stay readable after the content was consumed.
WARNING: `fileName()` is the name the client sent. Do not use it as a path without sanitizing it.
An upload belongs to the request. When the request ends, what the application did not consume is released, including the fields that were still waiting to be read when the reading stopped, and an operation that is still running is aborted. The work on an upload must therefore be part of the stage the method returns, as in the examples above.
WARNING: Do not close an upload in a try-with-resources block around an asynchronous operation: the block closes it as soon as the operation starts, which aborts the operation.
`close()` releases an upload early, and `FormData.close()` releases all the files of a form. The text fields stay readable.
== Streaming a Part
A api:http.form.FormPart[] argument receives a part of the form as it arrives, without storing it first:
snippet::io.micronaut.docs.server.formdata.ProfileController[tags="formPart", indent=0, title="Streaming a Part"]
<1> A text field sent before the part
<2> The `video` part, read as it arrives
<3> `transferTo` writes the content to a new file while it is received
The method runs once the part starts arriving, so the text fields it needs must be sent before the part. A field sent after the part cannot be bound, and the request is answered with `400 Bad Request`. A api:http.form.FormPart[] is a text field or a file: `isFile()` tells which, `text()` reads a text field, in the charset of the part like a file's `text`, and `file()` gives the part as a api:http.form.FileUpload[]. An `Optional<FormPart>` argument is empty when the form has no such part.
Like the reads of an api:http.body.AsyncRequestBody[], an operation on the part that is still running when the method completes, e.g. a `transferTo` whose stage the method did not return, is aborted and its buffers and files are released before the response is written, or, for a streamed response, when its stream ends. The work on the part must therefore be part of the stage the method returns. Content the method did not read is discarded when the request ends.
== Reading the Parts One by One
A api:http.form.FormParts[] argument reads the whole form part by part, in the order the client sent them:
snippet::io.micronaut.docs.server.formdata.ProfileController[tags="formParts", indent=0, title="Reading the Parts"]
<1> The method declares a api:http.form.FormParts[] argument
<2> `forEach` hands every part to the consumer, one at a time
<3> A file is written to a new file
<4> A text field is read, here with a limit of 1024 bytes
<5> The stage of `forEach` completes once every part was consumed
The consumer owns its part until the stage it returned completes. Then what it did not read is discarded, and the next part is read. `part(name, consumer)` reads forward to the part of a name, discarding the parts before it, and completes with `false` when the form has no such part. `closeAsync()` discards the rest of the form.
The parts are closed like the reads of an api:http.body.AsyncRequestBody[]: when the method completes, normally or exceptionally, before the response is written, or, for a method that answers with a streamed response, when its stream ends: completes, fails or is cancelled, e.g. because the client disconnected. An operation the method did not wait for is aborted then, the part it was consuming is released, and the rest of the form is discarded, so the work on the parts must be part of the stage the method returns, or of its stream.
An api:http.body.AsyncRequestBody[] argument reads a form too, with `form()` for a api:http.form.FormData[] and `parts()` for api:http.form.FormParts[], see <<asyncRequestBody, Reading the Body Asynchronously>>.
== Limits
A form is decoded as it arrives, so `micronaut.server.max-request-buffer-size` does not limit the form as a whole: it limits what is read into memory. This holds even for a limit smaller than what the server receives before the form is read, e.g. with the request headers. Only the form read from a copy of the body, `copy().form()` or `copy().parts()` of an api:http.body.AsyncRequestBody[], is held to the limit as a whole, see <<asyncRequestBody, Reading the Body Asynchronously>>.
* The text fields of a api:http.form.FormData[] are held in memory, and are limited together: a form whose text fields are larger than `micronaut.server.max-request-buffer-size` is answered with `413 Request Entity Too Large`. The text field and api:http.form.FileUpload[] arguments share that form, and its limit.
* The files of a api:http.form.FormData[] are stored as the `micronaut.server.multipart` configuration says, and each is limited by `micronaut.server.multipart.max-file-size`.
* The parts of api:http.form.FormParts[] and a api:http.form.FormPart[] are limited one by one, by the operation that reads them: `text()` reads up to `micronaut.server.max-request-buffer-size` bytes, `text(maximumBytes)` and `bytes(maximumBytes)` up to the given limit, and `transferTo` is not limited by the buffer size. A part larger than the limit of its read fails with `413 Request Entity Too Large`.
The request as a whole is limited by `micronaut.server.max-request-size`, and the Netty server limits the number of fields and the bytes the decoder buffers with its `micronaut.server.netty.form-max-fields` and `micronaut.server.netty.form-max-buffered-bytes` settings.
== Combining Arguments
A method can combine a api:http.form.FormData[] argument, api:http.form.FileUpload[] arguments and text field arguments, as they share one form. The other combinations read the form in ways that exclude each other, and the request is answered with `500 Internal Server Error` with a message that says the arguments "cannot be combined":
* a api:http.form.FormParts[] argument with any other form argument
* a api:http.form.FormData[] argument with a api:http.multipart.CompletedFileUpload[], a api:http.form.FormPart[] or a api:http.form.FormParts[] argument
== In a Request Bean
The members of a api:http.annotation.RequestBean[] are bound like the arguments of the method: a api:http.form.FormData[], a api:http.form.FileUpload[], a text field with api:http.annotation.Part[], a api:http.form.FormPart[], api:http.form.FormParts[], an api:http.body.AsyncRequestBody[] and api:http.PathVariables[] can be members of the bean. The method waits for a form that is still arriving before it is invoked with the bean, as it does for its arguments.
== In Filters
The request filter methods of a api:http.annotation.ServerFilter[] can declare the same arguments as a controller method: api:http.body.AsyncRequestBody[], api:http.form.FormData[], api:http.form.FormParts[], api:http.form.FormPart[], api:http.form.FileUpload[], a `List` or an `Optional` of those, and text fields with api:http.annotation.Part[]. A filter that reads the form with api:http.form.FormData[], api:http.form.FileUpload[] or api:http.annotation.Part[] arguments leaves the same form for the route:
snippet::io.micronaut.docs.server.formdata.TermsFilter[tags="imports,class", title="Reading the Form in a Filter"]
<1> The request filter method declares a api:http.form.FormData[] argument
<2> The filter reads a text field, and answers the request itself when the terms were not accepted
<3> Otherwise the request continues
snippet::io.micronaut.docs.server.formdata.SignupController[tags="imports,class", title="The Route Reads the Same Form"]
<1> The route binds the fields and files of the form the filter read, and can still consume the files
An api:http.body.AsyncRequestBody[] argument is different: its `form()` and `parts()` consume the body, like its other reads. So does a api:http.form.FormParts[] argument, once the filter reads a part, and its parts are closed when the filter method completes, like the reads of an api:http.body.AsyncRequestBody[]. A api:http.form.FormPart[] argument of a filter streams its part the same way: the fields before it are discarded, the filter runs once the part starts, and the rest of the form is discarded when the filter method completes. The route can then no longer read the form, and fails with `500 Internal Server Error` when it does, while the files the filter did not read are released when the request ends. A filter that reads the form of an api:http.body.AsyncRequestBody[] and leaves it for the route reads it from a copy: `copy().form()` is the same shared form as a api:http.form.FormData[] argument, read from a split of the body, and `copy().parts()` streams through the parts of a split of the body. The body keeps the bytes for the route, which can read them in any way, so the form is then held to `micronaut.server.max-request-buffer-size` as a whole. See <<asyncRequestBody, Reading the Body Asynchronously>>.
A filter that continues with a mutable request whose body it cleared, with `request.body(null)`, leaves an empty form for the route. A filter that sets a api:http.form.FormData[] as the body replaces the form.
NOTE: With `micronaut-http-validation` on the annotation processor path, these parameters and api:http.annotation.Part[] are compilation errors on a response filter method and on the methods of a api:http.annotation.ClientFilter[]. So is a api:http.annotation.Part[] parameter of a request filter method whose type is read with the arguments of the route, after the filters: api:http.multipart.CompletedFileUpload[], api:http.multipart.StreamingFileUpload[], api:http.multipart.CompletedPart[], api:http.multipart.PartData[] or a `Publisher`. A filter reads a file with a api:http.form.FileUpload[], a api:http.form.FormPart[] or a api:http.form.FormData[] argument instead.
---
### Src Main Docs Guide HttpServer FunctionalRoutes (`src/main/docs/guide/httpServer/functionalRoutes.adoc`)
WARNING: Functional routes are experimental. The API, in the package pkg:web.router.builder[] and the related types api:http.body.AsyncRequestBody[], api:http.body.BodyElements[] and the package pkg:http.form[], is annotated with ann:core.annotation.Experimental[] and may change in a future release.
Besides controllers, the Micronaut HTTP server routes requests to *handler functions* declared in code. A bean of type api:web.router.builder.HttpRoutes[] receives an api:web.router.builder.HttpRouteBuilder[] and declares its routes with it:
snippet::io.micronaut.docs.server.functional.HelloRoutes[tags="imports,clazz", indent=0, title="A handler route"]
A handler route is a route like the route of a controller method, not a separate request model. The router sees the handler as an executable method, so everything that applies to a controller route applies to a handler route:
* argument binding, the message body readers and writers, and content negotiation (the `405`, `415` and `406` answers);
* the application's filters (`@ServerFilter` beans and api:http.filter.HttpServerFilter[] beans), CORS and the implicit `HEAD` route of a `GET` route;
* the error routes: `@Error` methods and api:http.server.exceptions.ExceptionHandler[] beans, which receive checked exceptions unwrapped;
* the executors (`@ExecuteOn`, `@NonBlocking` and the thread selection of the server), and the propagated context.
Functional routes add what is natural to declare in code: groups of routes with a common path prefix and common filters, filters on a single route, request conditions, error routes local to a group, and routes to targets located at runtime.
Every `HttpRoutes` bean adds its routes next to the controllers, under `micronaut.server.context-path` if one is configured. Declare an `HttpRoutes` bean as a class, as above, or return a lambda from a factory method, as in the next section. The order of the `HttpRoutes` beans only orders the beans: it never decides which route answers a request.
WARNING: The router reads the routes, their configuration and their filters when it is created: the routes cannot change while the application runs. A builder, group or route specification kept after `HttpRoutes.routes(...)`, or after the lambda of its group, returned throws an `IllegalStateException` when it is used, and so does a filter of a group. The other specifications are not checked: a change to a filter declared with `serverFilter(...)` on the builder, or to an error or status route, after `HttpRoutes.routes(...)` returned is ignored.
---
### Src Main Docs Guide HttpServer Graphql (`src/main/docs/guide/httpServer/graphql.adoc`)
https://graphql.org/[GraphQL] is a query language for building APIs that provides an intuitive and flexible syntax and a system for describing data requirements and interactions.
See the documentation for https://micronaut-projects.github.io/micronaut-graphql/latest/guide[Micronaut GraphQL] for more information on how to build GraphQL applications with Micronaut.
---
### Src Main Docs Guide HttpServer HostResolution (`src/main/docs/guide/httpServer/hostResolution.adoc`)
You may need to resolve the host name of the current server. The Micronaut framework includes an implementation of the api:http.server.util.HttpHostResolver[] interface.
The default implementation looks for host information in the following places in order:
. The supplied configuration
. The `Forwarded` header
. `X-Forwarded-` headers. If the `X-Forwarded-Host` header is not present, the other `X-Forwarded` headers are ignored.
. The `Host` header
. The properties on the request URI
. The properties on the embedded server URI
The behavior of which headers to pull the relevant data can be changed with the following configuration:
include::{includedir}configurationProperties/io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration.adoc[]
The above configuration also supports an allowed host list. Configuring this list ensures any resolved host matches one of the supplied regular expression patterns. That is useful to prevent host cache poisoning attacks and is recommended to be configured.
---
### Src Main Docs Guide HttpServer Http2Server (`src/main/docs/guide/httpServer/http2Server.adoc`)
Since Micronaut framework 2.x, Micronaut Netty-based HTTP server can be configured to support HTTP/2.
==== Configuring the Server for HTTP/2
The first step is to set the supported HTTP version in the server configuration:
.Enabling HTTP/2 Support
[configuration]
----
micronaut:
server:
http-version: 2.0
----
With this configuration, the Micronaut framework enables support for the `h2c` protocol (see https://httpwg.org/specs/rfc7540.html#discover-http[HTTP/2 over cleartext]) which is fine for development.
Since browsers don't support `h2c` and in general https://httpwg.org/specs/rfc7540.html#discover-https[HTTP/2 over TLS] (the `h2` protocol), it is recommended for production that you enable <<https, HTTPS support>>. For development this can be done with:
.Enabling `h2` Protocol Support
[configuration]
----
micronaut:
server:
http-version: 2.0
ssl:
enabled: true
buildSelfSigned: true
----
For production, see the <<https, configuring HTTPS>> section of the documentation.
Note that if your deployment environment uses JDK 8, or for improved support for OpenSSL, define the following dependencies on Netty Tomcat Native:
dependency:io.netty:netty-tcnative:2.0.58.Final[scope="runtimeOnly"]
dependency:io.netty:netty-tcnative-boringssl-static:2.0.58.Final[scope="runtimeOnly"]
In addition to a dependency on the appropriate native library for your architecture. For example:
.Configuring Tomcat Native
[source,groovy]
----
runtimeOnly "io.netty:netty-tcnative-boringssl-static:2.0.58.Final:${Os.isFamily(Os.FAMILY_MAC) ? (Os.isArch("aarch64") ? "osx-aarch_64" : "osx-x86_64") : 'linux-x86_64'}"
----
See the documentation on https://netty.io/wiki/forked-tomcat-native.html[Tomcat Native] for more information.
==== HTTP/2 Server Push Support
Support for server push has been added in Micronaut framework 3.2. Server push allows for a single request to trigger multiple responses. This is most often used in the case of browser based resources. The goal is to improve latency for the client, because they do not have to request that resource manually anymore, and can save a round trip.
A new interface, api:http.PushCapableHttpRequest[], has been created to support the feature. Simply add a api:http.PushCapableHttpRequest[] parameter to a controller method and use its API to trigger additional requests.
NOTE: api:http.PushCapableHttpRequest[] extends api:http.HttpRequest[] so it's not necessary to have both as arguments in a controller method.
Before triggering additional requests, the `isServerPushSupported()` method should be called to ensure the feature is available. Once it's known the feature is supported, use the `serverPush(HttpRequest)` method to trigger additional requests. For example: `request.serverPush(HttpRequest.GET("/static/style.css"))`).
---
### Src Main Docs Guide HttpServer Http3Server (`src/main/docs/guide/httpServer/http3Server.adoc`)
Since Micronaut framework 4.x, Micronaut's Netty-based HTTP server can be configured to support HTTP/3.
==== Configuring the Server for HTTP/3
Instead of the TCP used for HTTP/1.1 and HTTP/2, HTTP/3 runs on UDP. To expose an HTTP/3 server, you need to define a <<listener, listener>> with the special `QUIC` protocol family:
.Enabling HTTP/3 Support
[configuration]
----
micronaut:
server:
netty:
listeners:
http3Listener:
family: QUIC
port: 8443
----
NOTE: that defining this listener will disable the implicit TCP listeners. You can add them manually as described in the <<listener, listener section>>.
Additionally, the netty HTTP/3 codec needs to be present on the classpath:
dependency:micronaut-http-netty-http3[]
---
### Src Main Docs Guide HttpServer JsonBinding (`src/main/docs/guide/httpServer/jsonBinding.adoc`)
The most common data interchange format nowadays is JSON.
By default, the api:http.annotation.Controller[] annotation specifies that the controllers in Micronaut framework consume and produce JSON by default.
Since Micronaut Framework 4.0, users must choose how they want to serialize (Jackson Databind or Micronaut Serialization). Both approaches allow the usage of https://micronaut-projects.github.io/micronaut-serialization/latest/guide/index.html#jacksonAnnotations[Jackson Annotations].
With either approach, the Micronaut framework reads incoming JSON in a non-blocking manner.
---
### Src Main Docs Guide HttpServer LocaleResolution (`src/main/docs/guide/httpServer/localeResolution.adoc`)
The Micronaut framework supports several strategies for resolving locales for a given request. The api:http.HttpRequest#getLocale--[] method is available on the request, however it only supports parsing the `Accept-Language` header. For other use cases where the locale can be in a cookie, the user's session, or should be set to a fixed value, api:http.server.util.locale.HttpLocaleResolver[] can be used to determine the current locale.
The api:core.util.LocaleResolver[] API does not need to be used directly. Simply define a parameter to a controller method of type `java.util.Locale` and the locale will be resolved and injected automatically.
There are several configuration options to control how to resolve the locale:
include::{includedir}configurationProperties/io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties.adoc[]
Locales can be configured in the "en_GB" format, or in the BCP 47 (Language tag) format. If multiple methods are configured, the fixed locale takes precedence, followed by session/cookie, then header.
If any of the built-in methods do not meet your use case, create a bean of type api:http.server.util.locale.HttpLocaleResolver[] and set its order (through the `getOrder` method) relative to the existing resolvers.
---
### Src Main Docs Guide HttpServer Openapi (`src/main/docs/guide/httpServer/openapi.adoc`)
To configure the Micronaut integration with https://swagger.io/docs/specification/about/[OpenAPI/Swagger], please follow
https://micronaut-projects.github.io/micronaut-openapi/latest/guide/index.html[these instructions]
---
### Src Main Docs Guide HttpServer PathVariables (`src/main/docs/guide/httpServer/pathVariables.adoc`)
Instead of one method argument per URI variable, a method can declare an argument of type api:http.PathVariables[]. It gives every variable of the route that matched the request, and converts a value when it is read.
NOTE: api:http.PathVariables[] is experimental. Its API may change in a future release.
snippet::io.micronaut.docs.server.pathvariables.ItemController[tags="imports,class,item,endclass", title="Reading the Path Variables"]
<1> The route has a required variable `id` and an optional variable `page`
<2> The method declares a api:http.PathVariables[] argument instead of one argument per variable
<3> `getLong` converts the value of a required variable
<4> `getInt` with a default value is used for an optional variable
A request to `/items/5` answers `Item 5, page 1`, and one to `/items/5/3` answers `Item 5, page 3`.
== Accessors
Values convert with the conversion service of the route, the same way as the api:http.annotation.PathVariable[] arguments of a method:
* `getString`, `getInt`, `getLong`, `getBoolean` and the accessors of the other primitive types read a required variable.
* The same accessors with a second argument, e.g. `getInt("page", 1)`, return the default value when the variable has no value.
* `findString`, `findInt`, `findLong`, `findDouble` and `findBoolean` return an empty optional when the variable has no value.
* `get(name, type)` and `find(name, type)` convert to any other type, e.g. `UUID`, and accept an api:core.type.Argument[] for a generic type.
* `names()` and `contains(name)` tell which variables have a value.
The values of an exploded variable, e.g. `/files{/path*}`, or of a comma separated value are read as a list:
snippet::io.micronaut.docs.server.pathvariables.ItemController[tags="lists", indent=0, title="Reading a List"]
<1> `getStrings` splits the value, so `/tags/red,green` gives `[red, green]`
<2> `getList` converts each value, so `/sum/1,2,3` gives `[1, 2, 3]`
== Errors
A required variable that has no value, or a value that does not convert, is answered with `400 Bad Request`, the same as for a api:http.annotation.PathVariable[] argument. With the example above, `/items/abc` is answered with `400`.
A method that declares api:http.PathVariables[] reads the variables itself, so the compile-time route validation does not report the variables of the URI template that have no argument of their own.
== In Filters
The request filter methods of a api:http.annotation.ServerFilter[] run after the request is routed, so they can declare api:http.PathVariables[] too:
snippet::io.micronaut.docs.server.pathvariables.ArchivedItemFilter[tags="imports,class", title="Reading the Path Variables in a Filter"]
<1> The filter applies to the `/items/**` routes
<2> The request filter method declares a api:http.PathVariables[] argument
<3> `findLong` reads the `id` variable of the route that matched the request, if it has one
<4> The filter answers the request itself, here with `410 Gone`
WARNING: A api:http.server.annotation.PreMatching[] filter method runs before the request is routed, so there are no path variables yet. With `micronaut-http-validation` on the annotation processor path, a api:http.PathVariables[] parameter of a `@PreMatching` filter method or of a api:http.annotation.ClientFilter[] method is a compilation error.
---
### Src Main Docs Guide HttpServer PlainTextResponses (`src/main/docs/guide/httpServer/plainTextResponses.adoc`)
By default, a Micronaut Controller responds with content-type `application/json`. However, you can respond with content type `text/plain` by annotating the controller method with the ann:http.annotation.Produces[] annotation.
snippet::io.micronaut.docs.http.server.response.textplain.TextPlainController[tags="classopening,method,classclosing", title="HTTP Response with text/plain Content-Type"]
<1> The Controller endpoint specifies a response's Content-Type of `text/plain`.
<2> The endpoint returns type `String`, and the implementation explicitly converts the data to a string using the `toString()` method.
NOTE: Micronaut Framework 4.x `text/plain` responses are more restrictive about allowed types than Micronaut Framework 3.x. To return plain text responses for answers other than `java.lang.String`, manually call the object `toString()` method. Alternatively, set the `micronaut.http.legacy-text-conversion` configuration option to `true` to restore the old β but not recommended β Micronaut Framework 3.x behavior.
---
### Src Main Docs Guide HttpServer ProducesAnnotation (`src/main/docs/guide/httpServer/producesAnnotation.adoc`)
A Micronaut controller action produces `application/json` by default. However, you can change the `Content-Type` of the response with the `@Produces` annotation or the `produces` member of the HTTP method annotations.
snippet::io.micronaut.docs.server.response.ProducesController[tags="imports,clazz", indent=0]
<1> The default content type is JSON
<2> Annotate a controller action with `@Produces` to change the response content type.
<3> Setting the `produces` member of the method annotation also changes the content type.
---
### Src Main Docs Guide HttpServer ReactiveServer (`src/main/docs/guide/httpServer/reactiveServer.adoc`)
As mentioned previously, Micronaut framework is built on Netty which is designed around an Event loop model and non-blocking I/O. Micronaut executes code defined in ann:http.annotation.Controller[] beans in the same thread as the request thread (an Event Loop thread).
This makes it critical that if you do any blocking I/O operations (for example interactions with Hibernate/JPA or JDBC) that you offload those tasks to a separate thread pool that does not block the Event loop.
For example the following configuration configures the I/O thread pool as a fixed thread pool with 75 threads (similar to what a traditional blocking server such as Tomcat uses in the thread-per-request model):
.Configuring the IO thread pool
[configuration]
----
micronaut:
executors:
io:
type: fixed
nThreads: 75
----
To use this thread pool in a ann:http.annotation.Controller[] bean you have a number of options. The simplest is to use the ann:scheduling.annotation.ExecuteOn[] annotation, which can be applied only to either a ann:http.annotation.Controller[] or ann:http.annotation.Filter[] at the type or method level. It indicates the configured thread pool to run method(s) of the controller or filter on:
snippet::io.micronaut.docs.http.server.executeon.PersonController[tags="imports,class", indent=0,title="Using @ExecuteOn"]
<1> The ann:scheduling.annotation.ExecuteOn[] annotation is used to execute the operation on the I/O thread pool
The value of the ann:scheduling.annotation.ExecuteOn[] annotation can be any named executor defined under `micronaut.executors`.
TIP: Generally speaking for database operations you want a thread pool configured that matches the maximum number of connections specified in the database connection pool.
For a Kotlin `suspend` function, the selected executor is also the dispatcher the coroutine resumes on, so work after a suspension point stays on that executor rather than falling back to `Dispatchers.Default`. Child coroutines inherit it, and an explicit `withContext` still wins for the duration of its block.
NOTE: An explicit ann:scheduling.annotation.ExecuteOn[] and the `micronaut.server.thread-selection` setting both resolve through the same executor selection, so a `suspend` route is offloaded under `AUTO`, `IO` and `BLOCKING` thread selection just as a blocking route is. To keep a `suspend` route on the event loop under `AUTO`, annotate it with ann:core.annotation.NonBlocking[].
An alternative to the ann:scheduling.annotation.ExecuteOn[] annotation is to use the facility provided by the reactive library you have chosen. Reactive implementations such as https://projectreactor.io[Project Reactor] or https://github.com/ReactiveX/RxJava[RxJava] feature a `subscribeOn` method which lets you alter which thread executes user code. For example:
snippet::io.micronaut.docs.http.server.reactive.PersonController[tags="imports,class", indent=0,title="Reactive subscribeOn Example"]
<1> The configured I/O executor service is injected
<2> The `Mono::fromCallable` method wraps the blocking operation
<3> The https://projectreactor.io[Project Reactor] `subscribeOn` method schedules the operation on the I/O thread pool
---
### Src Main Docs Guide HttpServer RequestResponse (`src/main/docs/guide/httpServer/requestResponse.adoc`)
If you need more control over request processing you can write a method that receives the complete api:io.micronaut.http.HttpRequest[].
In fact, there are several higher-level interfaces that can be bound to controller method parameters. These include:
.Bindable Micronaut Interfaces
|===
|Interface |Description |Example
|api:io.micronaut.http.HttpRequest[]
|The full `HttpRequest`
|`String hello(HttpRequest request)`
|api:io.micronaut.http.HttpHeaders[]
|All HTTP headers present in the request
|`String hello(HttpHeaders headers)`
|api:io.micronaut.http.HttpParameters[]
|All HTTP parameters (either from URI variables or request parameters) present in the request
|`String hello(HttpParameters params)`
|api:io.micronaut.http.cookie.Cookies[]
|All Cookies present in the request
|`String hello(Cookies cookies)`
|===
IMPORTANT: The api:io.micronaut.http.HttpRequest[] should be declared parametrized with a concrete generic type if the request body is needed, e.g. `HttpRequest<MyClass> request`. The body may not be available from the request otherwise.
In addition, for full control over the emitted HTTP response you can use the static factory methods of the api:io.micronaut.http.HttpResponse[] class which return a api:io.micronaut.http.MutableHttpResponse[].
The following example implements the previous `MessageController` example using the api:io.micronaut.http.HttpRequest[] and api:io.micronaut.http.HttpResponse[] objects:
snippet::io.micronaut.docs.server.request.MessageController[tags="imports,class,request,endclass", indent=0, title="Request and Response Example"]
<1> The method is mapped to the URI `/hello` and accepts a api:io.micronaut.http.HttpRequest[]
<2> The api:http.HttpRequest[] is used to obtain the value of a query parameter named `name`.
<3> The api:http.HttpResponse.ok(T)[] method returns a api:http.MutableHttpResponse[] with a text body. A header named `X-My-Header` is also added to the response.
The api:io.micronaut.http.HttpRequest[] is also available from a static context via api:io.micronaut.http.context.ServerRequestContext[].
snippet::io.micronaut.docs.server.request.MessageController[tags="imports,class,static-request,endclass", indent=0, title="Using the ServerRequestContext"]
<1> The api:io.micronaut.http.context.ServerRequestContext[] is used to retrieve the request.
IMPORTANT: Generally api:io.micronaut.http.context.ServerRequestContext[] is available within reactive flow, but the recommended approach is consumed the request as an argument as shown in the previous example. If the request is needed in downstream methods it should be passed as an argument to those methods. There are cases where the context is not propagated because other threads are used to emit the data.
An alternative for users of Project Reactor to using the ServerRequestContext is to use the contextual features of Project Reactor to retrieve the request. Because the Micronaut Framework uses Project Reactor as it's default reactive streams implementation, users of Project Reactor can benefit by being able to access the request in the context. For example:
snippet::io.micronaut.docs.server.request.MessageController[tags="imports,class,request-context,endclass", indent=0, title="Using the Project Reactor context"]
<1> The Mono is created with a reference to the context
<2> The request is retrieved from the context
Using the context to retrieve the request is the best approach for reactive flows because Project Reactor propagates the context, and it does not rely on a thread local like api:io.micronaut.http.context.ServerRequestContext[].
---
### Src Main Docs Guide HttpServer Routing (`src/main/docs/guide/httpServer/routing.adoc`)
The link:{api}/io/micronaut/http/annotation/Controller.html[@Controller] annotation used in the previous section is one of link:{api}/io/micronaut/http/annotation/package-summary.html[several annotations] that allow you to control the construction of HTTP routes.
== URI Paths
The value of the `@Controller` annotation is an https://tools.ietf.org/html/rfc6570[RFC-6570 URI template], so you can embed URI variables within the path using the syntax defined by the URI template specification.
NOTE: Many other frameworks, including Spring, implement the URI template specification
The actual implementation is handled by the api:http.uri.UriMatchTemplate[] class, which extends api:http.uri.UriTemplate[].
You can use this class in your applications to build URIs, for example:
snippet::io.micronaut.docs.server.uris.UriTemplateTest[tags="match", title="Using a UriTemplate",indent=0]
<1> Use the `match` method to match a path
<2> Use the `expand` method to expand a template into a URI
You can use api:http.uri.UriTemplate[] to build paths to include in your responses.
== URI Path Variables
URI variables can be referenced via method arguments. When the path variable matches the method argument name, they are bound together automatically. If you want to use different names or specify a default value for a missing URI Variable, the link:{api}/io/micronaut/http/annotation/PathVariable.html[PathVariable] annotation can be used. The following example illustrates these options:
snippet::io.micronaut.docs.server.routes.IssuesController[tags="imports,startclass,normal,endclass", title="URI Variables Example"]
<1> The `@Controller` annotation is specified with a base URI of `/issues`
<2> The link:{api}/io/micronaut/http/annotation/Get.html[Get] annotation maps the method to an HTTP link:{api}/io/micronaut/http/HttpMethod.html#GET[GET] with a URI variable embedded in the URI named `number`
<3> The method argument `number` is bound automatically to the path variable `{number}` because the names match
<4> The value of the URI variable is referenced in the implementation
<5> The method argument requires the link:{api}/io/micronaut/http/annotation/PathVariable.html[PathVariable] annotation when method argument and path variable names don't match
The Micronaut framework maps the URI `/issues/{number}` for the above controller. We can assert this is the case by writing unit tests:
snippet::io.micronaut.docs.server.routes.IssuesControllerTest[tags="imports,startclass,normal,endclass", title="Testing URI Variables"]
<1> The embedded server and HTTP client are started
<2> The server and client are cleaned up after the tests finish
<3> The tests send a request to the URI `/issues/12`
<4> And then asserts the response is "Issue # 12"
<5> Another test using the end point defined with `@PathVariable` asserts the response is "Issue # 13"
<6> Another test asserts a 400 response is returned when an invalid number is sent in the URL
<7> Another test asserts a 404 response is returned when no number is provided in the URL. The variable being present is required for the route to be executed.
Note that the URI template in the previous example requires that the `number` variable is specified. You can specify optional URI templates with the syntax: `/issues{/number}` and by annotating the `number` parameter with `@Nullable`. Alternatively, you can use the `defaultValue` element of the link:{api}/io/micronaut/http/annotation/PathVariable.html[PathVariable] annotation to specify a default value when the URI variable is missing. For example:
snippet::io.micronaut.docs.server.routes.IssuesController[tags="defaultvalue", indent=0, title="Default Value Example"]
<1> The forward slash inside the braces designates `number` as an optional URI variable
<2> The `defaultValue` attribute specifies the default value for `number` when the URI variable is missing
snippet::io.micronaut.docs.server.routes.IssuesControllerTest[tags="defaultvalue", indent=0, title="Testing Default Value"]
<1> This test illustrates the substitution of a default `PathVariable` value when the URI variable is missing
<2> And another test to illustrate when the optional URI variable is provided
TIP: To read all the variables of the route with one argument, see <>.
The following table provides examples of URI templates and what they match:
.URI Template Matching
|===
|Template |Description|Matching URI
|`/books/{id}`
| Simple match
| `/books/1`
|`/books/{id:2}`
| A variable of two characters max
| `/books/10`
|`/books{/id}`
| An optional URI variable
| `/books/10` or `/books`
| `/book{/id:[a-zA-Z]+}`
| An optional URI variable with regex
| `/books/foo`
| `/books{?max,offset}`
| Optional query parameters
| `/books?max=10&offset=10`
| `/books{/path:.*}{.ext}`
| Regex path match with extension
| `/books/foo/bar.xml`
|===
== URI Reserved Character Matching
By default, URI variables as defined by the https://tools.ietf.org/html/rfc6570[RFC-6570 URI template] spec cannot include reserved characters such as `/`, `?` etc.
This can be problematic if you wish to match or expand entire paths. As per https://tools.ietf.org/html/rfc6570#section-3.2.3[section 3.2.3 of the specification], you can use reserved expansion or matching using the `+` operator.
For example the URI `/books/{\+path}` matches both `/books/foo` and `/books/foo/bar` since the `+` indicates that the variable `path` should include reserved characters (in this case `/`).
== Routing Annotations
The previous example uses the link:{api}/io/micronaut/http/annotation/Get.html[@Get] annotation to add a method that accepts HTTP link:{api}/io/micronaut/http/HttpMethod.html#GET[GET] requests. The following table summarizes the available annotations and how they map to HTTP methods:
.HTTP Routing Annotations
|===
|Annotation |HTTP Method
|link:{api}/io/micronaut/http/annotation/Delete.html[@Delete]
|link:{api}/io/micronaut/http/HttpMethod.html#DELETE[DELETE]
|link:{api}/io/micronaut/http/annotation/Get.html[@Get]
|link:{api}/io/micronaut/http/HttpMethod.html#GET[GET]
|link:{api}/io/micronaut/http/annotation/Head.html[@Head]
|link:{api}/io/micronaut/http/HttpMethod.html#HEAD[HEAD]
|link:{api}/io/micronaut/http/annotation/Options.html[@Options]
|link:{api}/io/micronaut/http/HttpMethod.html#OPTIONS[OPTIONS]
|link:{api}/io/micronaut/http/annotation/Patch.html[@Patch]
|link:{api}/io/micronaut/http/HttpMethod.html#PATCH[PATCH]
|link:{api}/io/micronaut/http/annotation/Put.html[@Put]
|link:{api}/io/micronaut/http/HttpMethod.html#PUT[PUT]
|link:{api}/io/micronaut/http/annotation/Post.html[@Post]
|link:{api}/io/micronaut/http/HttpMethod.html#POST[POST]
|link:{api}/io/micronaut/http/annotation/Trace.html[@Trace]
|link:{api}/io/micronaut/http/HttpMethod.html#TRACE[TRACE]
|===
NOTE: All the method annotations default to `/`.
== Route Conditions
The api:http.annotation.RouteCondition[] annotation allows you to define a condition that must evaluate to `true` for a route to match a request. The condition is specified as a Micronaut Expression Language (EL) expression and is evaluated at request time.
Within the expression, a `request` variable is available that references the current api:http.HttpRequest[].
This is useful for routing requests based on query parameters, headers, or any other aspect of the request. For example, you can use `@RouteCondition` to route requests to different methods depending on the value of a query parameter:
snippet::io.micronaut.docs.server.routing.RouteConditionController[tags="imports,class", indent=0, title="Route Condition Example"]
<1> The route condition expression evaluates whether the `v` query parameter equals `2`. If it does, the request is routed to `helloV2()`. If not, the route is not matched and the request falls through to `helloV1()`.
NOTE: The api:http.annotation.RouteCondition[] annotation only applies to server-side routes and is ignored when placed on declarative HTTP client routes.
TIP: The `request` variable in the expression is an instance of api:http.HttpRequest[]. You can access headers via `request.headers`, query parameters via `request.parameters`, and other request attributes as needed.
== @Options
<<cors, CORS>> support handles OPTIONS preflight requests. However, if you want to dispatch OPTIONS requests without an Origin HTTP Header, you can enable it via:
[configuration]
----
micronaut:
server:
dispatch-options-requests: true
----
== Multiple URIs
Each of the routing annotations supports multiple URI templates. For each template, a route is created. This feature is useful for example to change the path of the API and leave the existing path as is for backwards compatibility. For example:
snippet::io.micronaut.docs.server.routing.BackwardCompatibleController[tags="imports,class", indent=0, title="Multiple URIs"]
<1> Specify multiple templates
<2> Bind to the template arguments as normal
NOTE: Route validation is more complicated with multiple templates. If a variable that would normally be required does not exist in all templates, that variable is considered optional since it may not exist for every execution of the method.
== Building Routes Programmatically
[.lang-java.lang-groovy.lang-kotlin]
--
If you prefer to not use annotations and instead declare all routes in code then never fear, the Micronaut framework has a flexible link:{api}/io/micronaut/web/router/RouteBuilder.html[RouteBuilder] API that makes it a breeze to define routes programmatically.
To start, subclass link:{api}/io/micronaut/web/router/DefaultRouteBuilder.html[DefaultRouteBuilder] and inject the controller to route to into the method, and define your routes:
--
snippet::io.micronaut.docs.server.routes.MyRoutes[tags="imports,class", indent=0, title="URI Variables Example"]
[.lang-java.lang-groovy.lang-kotlin]
--
<1> Route definitions should subclass link:{api}/io/micronaut/web/router/DefaultRouteBuilder.html[DefaultRouteBuilder]
<2> Use `@Inject` to inject a method with the controller to route to
<3> Use methods such as link:{api}/io/micronaut/web/router/RouteBuilder.html[`RouteBuilder::GET(String,Class,String,Class...)`] to route to controller methods. Note that even though the issues controller is used, the route has no knowledge of its `@Controller` annotation and thus the full path must be specified.
TIP: Unfortunately due to type erasure, a Java method lambda reference cannot be used with the API. For Groovy there is a `GroovyRouteBuilder` class which can be subclassed that allows passing Groovy method references.
--
[.lang-python]
--
Python applications declare routes as module-level functions instead of subclassing a route builder. See <<moduleScripts, Module Scripts>> for how a module of route functions is turned into a controller.
--
== Route Compile-Time Validation
The Micronaut framework supports validating route arguments at compile time with the validation library. To get started, add the `micronaut-http-validation` dependency to your build:
dependency:io.micronaut:micronaut-http-validation[scope='annotationProcessor']
With the correct dependency on your classpath, route arguments will automatically be checked at compile time. Compilation will fail if any of the following conditions are met:
* {blank}
The URI template contains a variable that is optional, but the method parameter is not annotated with `@Nullable` or is an `java.util.Optional`.
An optional variable is one that allows the route to match a URI even if the value is not present. For example `/foo{/bar}` matches requests to `/foo` and `/foo/abc`. The non-optional variant would be `/foo/{bar}`. See the <<_uri_path_variables, URI Path Variables>> section for more information.
* {blank}
The URI template contains a variable that is missing from the method arguments.
NOTE: To disable route compile-time validation, set the system property `-Dmicronaut.route.validation=false`. For Java and Kotlin users using Gradle, the same effect can be achieved by removing the `micronaut-http-validation` dependency from the `annotationProcessor`/`kapt` scope.
== Routing non-standard HTTP methods
The `@CustomHttpMethod` annotation supports non-standard HTTP methods for a client or server. Specifications like https://tools.ietf.org/html/rfc4918[RFC-4918 Webdav] require additional methods like REPORT or LOCK for example.
.RoutingExample
[source,java]
----
@CustomHttpMethod(method = "LOCK", value = "/{name}")
String lock(String name)
----
The annotation can be used anywhere the standard method annotations can be used, including controllers and declarative HTTP clients.
== RouteMatch
The api:web.router.RouteMatch[] API provides information about an executable api:web.router.Route[].
Given a request you can retrieve a RouteMatch with:
snippet::io.micronaut.docs.web.router.routematch.RouteMatchTest[tags="routematch", indent=0]
---
### Src Main Docs Guide HttpServer RunningServer (`src/main/docs/guide/httpServer/runningServer.adoc`)
To run the server, create an `Application` class with a `static void main` method, for example:
.Micronaut Application Class
snippet::io.micronaut.docs.server.intro.Application[tags="imports,class",indent=0]
To run the application from a unit test, use the link:{api}/io/micronaut/runtime/server/EmbeddedServer.html[EmbeddedServer] interface:
.Micronaut Test Case
snippet::io.micronaut.docs.server.intro.HelloControllerSpec[tags="imports,class", indent=0]
<1> The `EmbeddedServer` is run and the Spock `@AutoCleanup` annotation ensures the server is stopped after the specification completes.
<2> The `EmbeddedServer` interface provides the `URL` of the server under test which runs on a random port.
<3> The test uses the Micronaut HTTP client to make the call
<4> The `retrieve` method returns the response of the controller as a `String`
IMPORTANT: Without explicit port configuration, the port will be 8080, unless the application is run under the `test` environment where the port is random. When the application context starts from the context of a test class, the test environment is added automatically.
---
### Src Main Docs Guide HttpServer RunningSpecificPort (`src/main/docs/guide/httpServer/runningSpecificPort.adoc`)
By default, the server runs on port 8080. However, you can set the server to run on a specific port:
[configuration]
----
micronaut:
server:
port: 8086
----
TIP: This is also configurable from an environment variable, e.g. `MICRONAUT_SERVER_PORT=8086`
To run on a random port:
[configuration]
----
micronaut:
server:
port: -1
----
TIP: Setting an explicit port may cause tests to fail if multiple servers start simultaneously on the same port. To prevent that, specify a random port in the test environment configuration.
---
### Src Main Docs Guide HttpServer ServerConfiguration (`src/main/docs/guide/httpServer/serverConfiguration.adoc`)
The HTTP server features a number of configuration options. They are defined in the api:http.server.netty.configuration.NettyHttpServerConfiguration[] configuration class, which extends api:http.server.HttpServerConfiguration[].
Netty beans are enabled by default. Set `netty.enabled=false` to disable all Netty integration, or `micronaut.server.netty.enabled=false` to disable only the Netty server. These properties back api:http.netty.RequiresNetty[] and api:http.server.netty.RequiresNettyServer[].
The following example shows how to tweak configuration options for the server via your configuration file (e.g `application.yml`):
.Configuring HTTP server settings
[configuration]
----
micronaut:
server:
maxRequestSize: 1MB
host: localhost
netty:
maxHeaderSize: 500KB
worker:
threads: 8
childOptions:
autoRead: true
----
- By default, Micronaut framework binds to all network interfaces. Use `localhost` to bind only to loopback network interface
- `maxHeaderSize` sets the maximum header size
- `worker.threads` specifies the number of Netty worker threads
- `autoRead` enables request body auto read
include::{includedir}configurationProperties/io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration.adoc[]
=== HTTP/1.x Protocol Settings
Two settings of the Netty server, both off by default, are useful when the server sits in front of other servers as a proxy:
[configuration]
----
micronaut:
server:
http10-keep-alive: true
reject-unsupported-http-versions: true
----
- `http10-keep-alive` keeps the connection of an HTTP/1.0 client that sends `Connection: keep-alive` after a response of known length, and answers with `Connection: keep-alive`. A response has a known length when it has a `Content-Length` or has no body, e.g. a `204` or `304` response or the response to a `HEAD` request. A response of unknown length still ends with the connection. By default, the connection of an HTTP/1.0 client ends after each response.
- `reject-unsupported-http-versions` answers a request of an HTTP major version other than 1, e.g. `HTTP/9.9` or `HTTP/2.0` on an HTTP/1 connection, which the request decoder accepts, with `505 HTTP Version Not Supported` over HTTP/1.1 and closes the connection. A later minor version of HTTP/1, e.g. `HTTP/1.2`, is served as HTTP/1.1 (RFC 9112 section 2.3). By default, such a request is served and answered with its own version.
=== Using Native Transports
The native Netty transports add features specific to a particular platform, generate less garbage, and generally improve performance when compared to the NIO-based transport.
To enable native transports, first add a dependency:
For macOS on x86:
dependency:netty-transport-native-kqueue[groupId="io.netty",scope="runtimeOnly",classifier="osx-x86_64"]
For macOS on M1:
dependency:netty-transport-native-kqueue[groupId="io.netty",scope="runtimeOnly",classifier="osx-aarch_64"]
For Linux on x86:
dependency:netty-transport-native-epoll[groupId="io.netty",scope="runtimeOnly",classifier="linux-x86_64"]
or
dependency:netty-transport-native-io_uring[groupId="io.netty",scope="runtimeOnly",classifier="linux-x86_64"]
For Linux on ARM64:
dependency:netty-transport-native-epoll[groupId="io.netty",scope="runtimeOnly",classifier="linux-aarch_64"]
or
dependency:netty-transport-native-io_uring[groupId="io.netty",scope="runtimeOnly",classifier="linux-aarch_64"]
Then configure the default event loop group to prefer native transports:
.Configuring The Default Event Loop to Prefer Native Transports
[configuration]
----
micronaut:
netty:
event-loops:
default:
transport: io_uring,epoll,kqueue,nio
----
The first available transport out of those listed will be used. If you want to make sure native transports will be used in all environments, remove the `nio` entry. An exception will be thrown if no native transport is available.
NOTE: Netty enables simplistic sampling resource leak detection which reports there is a leak or not, at the cost of small overhead. You can enable it by setting property `netty.resource-leak-detector-level` to one of: `DISABLED` (default), `SIMPLE`, `PARANOID` or `ADVANCED`.
---
### Src Main Docs Guide HttpServer ServerEvents (`src/main/docs/guide/httpServer/serverEvents.adoc`)
The HTTP server emits a number of <<events, Bean Events>>, defined in the pkg:io.micronaut.runtime.server.event[] package, that you can write listeners for. The following table summarizes these:
.Server Events
|===
|Event|Description
|api:runtime.server.event.ServerStartupEvent[]
|Emitted when the server completes startup
|api:runtime.server.event.ServerShutdownEvent[]
|Emitted when the server shuts down
|api:discovery.event.ServiceReadyEvent[]
|Emitted after all api:runtime.server.event.ServerStartupEvent[] listeners have been invoked and exposes the api:discovery.EmbeddedServerInstance[]
|api:discovery.event.ServiceStoppedEvent[]
|Emitted after all api:runtime.server.event.ServerShutdownEvent[] listeners have been invoked and exposes the api:discovery.EmbeddedServerInstance[]
|api:http.context.event.HttpRequestReceivedEvent[]
|Emitted for every request the server receives, before the request is routed. Exposes the request without its body.
|api:http.context.event.HttpRequestTerminatedEvent[]
|Emitted for every request once the server is done with it. This does not imply that a response was delivered.
|===
WARNING: Doing significant work within a listener for a api:runtime.server.event.ServerStartupEvent[] will increase startup time.
WARNING: Listeners for api:http.context.event.HttpRequestReceivedEvent[] and api:http.context.event.HttpRequestTerminatedEvent[] are invoked on the thread the server selects for request handling, which with the default `micronaut.server.thread-selection` (`MANUAL`, and also with `AUTO`) is the Netty event loop of the connection. They must not block: a slow listener delays every other connection on that event loop. Annotate the `@EventListener` method with ann:scheduling.annotation.Async[] to run it on an executor, or set `micronaut.server.thread-selection` to `BLOCKING` to move listeners (and controllers) off the event loop. With debug logging enabled for `io.micronaut.http.server.netty.RoutingInBoundHandler`, the server logs listeners that take longer than 100 ms on the event loop.
The following example defines a api:context.event.ApplicationEventListener[] that listens for api:runtime.server.event.ServerStartupEvent[]:
.Listening for Server Startup Events
[source,java]
----
import io.micronaut.context.event.ApplicationEventListener;
...
@Singleton
public class StartupListener implements ApplicationEventListener<ServerStartupEvent> {
@Override
public void onApplicationEvent(ServerStartupEvent event) {
// logic here
...
}
}
----
Alternatively, you can also use the ann:runtime.event.annotation.EventListener[] annotation on a method of any bean that accepts `ServerStartupEvent`:
.Using `@EventListener` with `ServerStartupEvent`
[source,java]
----
import io.micronaut.runtime.event.annotation.EventListener;
import io.micronaut.runtime.server.event.ServerStartupEvent;
import jakarta.inject.Singleton;
...
@Singleton
public class MyBean {
@EventListener
public void onStartup(ServerStartupEvent event) {
// logic here
...
}
}
----
---
### Src Main Docs Guide HttpServer ServerIO (`src/main/docs/guide/httpServer/serverIO.adoc`)
=== Reactively Writing Response Data
Micronaut's HTTP server supports writing chunks of response data by returning a rs:Publisher[] that emits objects that can be encoded to the HTTP response.
The following table summarizes example return type signatures and the behaviour the server exhibits to handle them:
|===
|Return Type | Description
|`Publisher<String>`
|A rs:Publisher[] that emits each chunk of content as a String
|`Flux`
|A reactor:Flux[] that emits each chunk of content as a `byte[]` without blocking
|`Flux`
|A Reactor `Flux` that emits each chunk as a Netty `ByteBuf`
|`Flux`
|When emitting a POJO, each emitted object is encoded as JSON by default without blocking
|`Flowable`
|A reactor:Flux[] that emits each chunk of content as a `byte[]` without blocking
|`Flowable`
|A Reactor `Flux` that emits each chunk as a Netty `ByteBuf`
|`Flowable`
|When emitting a POJO, each emitted object is encoded as JSON by default without blocking
|===
When returning a reactive type, the server uses a `Transfer-Encoding` of `chunked` and keeps writing data until the rs:Publisher[] `onComplete` method is called.
The server requests a single item from the rs:Publisher[], writes it, and requests the next, controlling back pressure.
NOTE: It is up to the implementation of the rs:Publisher[] to schedule any blocking I/O work that may be done as a result of subscribing to the publisher.
NOTE: To use https://projectreactor.io[Project Reactor]'s `Flux` or `Mono` you need to add the Micronaut Reactor dependency to your project to include the necessary converters.
NOTE: To use https://github.com/ReactiveX/RxJava[RxJava]'s `Flowable`, `Single` or `Maybe` you need to add the Micronaut RxJava dependency to your project to include the necessary converters.
=== Performing Blocking I/O
In some cases you may wish to integrate a library that does not support non-blocking I/O.
==== Writable
In this case you can return a api:core.io.Writable[] object from any controller method. The api:core.io.Writable[] interface has various signatures that allow writing to traditional blocking streams like link:{jdkapi}/java.base/java/io/Writer.html[Writer] or link:{jdkapi}/java.base/java/io/OutputStream.html[OutputStream].
When returning a api:core.io.Writable[], the blocking I/O operation is shifted to the I/O thread pool so the Netty event loop is not blocked.
TIP: See the section on configuring <> for details on how to configure the I/O thread pool to meet your application requirements.
The following example demonstrates how to use this API with Groovy's `SimpleTemplateEngine` to write a server side template:
snippet::io.micronaut.docs.writable.TemplateController[tags="imports,clazz", indent=0, title="Performing Blocking I/O With Writable"]
<1> The controller creates a simple template
<2> The controller method returns a api:core.io.Writable[]
<3> The returned function receives a link:{jdkapi}/java.base/java/io/Writer.html[Writer] and calls `writeTo` on the template.
==== InputStream
Another option is to return an input stream. This is useful for many scenarios that interact with other APIs that expose a stream.
snippet::io.micronaut.docs.http.server.stream.StreamController[tags="write", indent=0, title="Performing Blocking I/O With InputStream"]
<2> The input stream is returned and its contents will be the response body
NOTE: The reading of the stream will be offloaded to the IO thread pool if the controller method is executed on the event loop.
=== 404 Responses
Often, you want to respond 404 (Not Found) when you don't find an item in your persistence layer or in similar scenarios.
See the following example:
snippet::io.micronaut.docs.respondingnotfound.BooksController[tags="clazz", indent=0]
<1> Returning `null` triggers a 404 (Not Found) response.
<2> Returning an empty `Mono` triggers a 404 (Not Found) response.
NOTE: Responding with an empty `Publisher` or `Flux` is a streaming response, which results in an empty array being returned if the content type is JSON. Annotate the method with api:core.async.annotation.SingleResult[] to disable streaming.
To disable 404 on a null body or an empty publisher set `micronaut.server.not-found-on-missing-body` to `false`.
---
### Src Main Docs Guide HttpServer Sessions (`src/main/docs/guide/httpServer/sessions.adoc`)
See the documentation for link:{micronautsessiondocs}[Micronaut Session] for information about supporting HTTP sessions in your applications.
---
### Src Main Docs Guide HttpServer Sse (`src/main/docs/guide/httpServer/sse.adoc`)
The Micronaut HTTP server supports emitting https://en.wikipedia.org/wiki/Server-sent_events[Server Sent Events (SSE)] using the api:http.sse.Event[] API.
To emit events from the server, return a Reactive Streams rs:Publisher[] that emits objects of type api:http.sse.Event[].
The rs:Publisher[] itself could publish events from a background task, via an event system, etc.
Imagine for an example an event stream of news headlines; you may define a data class as follows:
snippet::io.micronaut.docs.server.sse.Headline[tags="class", indent=0,title="Headline"]
To emit news headline events, write a controller that returns a rs:Publisher[] of api:http.sse.Event[] instances using whichever Reactive library you prefer. The example below uses https://projectreactor.io[Project Reactor]'s reactor:Flux[] via the `generate` method:
snippet::io.micronaut.docs.server.sse.HeadlineController[tags="imports,class", indent=0,title="Publishing Server Sent Events from a Controller"]
<1> The controller method returns a rs:Publisher[] of api:http.sse.Event[]
<2> A headline is emitted for each version of Micronaut
<3> The reactor:Flux[] type's `generate` method generates a rs:Publisher[]. The `generate` method accepts an initial value and a lambda that accepts the value and a rx:Emitter[]. Note that this example executes on the same thread as the controller action, but you could use `subscribeOn` or map an existing "hot" reactor:Flux[].
<4> The rx:Emitter[] interface `onNext` method emits objects of type api:http.sse.Event[]. The api:http.sse.Event.of(ET)[] factory method constructs the event.
<5> The rx:Emitter[] interface `onComplete` method indicates when to finish sending server sent events.
NOTE: You typically want to schedule SSE event streams on a separate executor. The previous example uses ann:scheduling.annotation.ExecuteOn[] to execute the stream on the I/O executor.
The above example sends back a response of type `text/event-stream` and for each api:http.sse.Event[] emitted the `Headline` type previously will be converted to JSON resulting in responses such as:
.Server Sent Event Response Output
[source,json]
----
data: {"title":"Micronaut 1.0 Released","description":"Come and get it"}
data: {"title":"Micronaut 2.0 Released","description":"Come and get it"}
----
You can use the methods of the api:http.sse.Event[] interface to customize the Server Sent Event data sent back, including associating event ids, comments, retry timeouts, etc.
---
### Src Main Docs Guide HttpServer StaticResources (`src/main/docs/guide/httpServer/staticResources.adoc`)
Static resource resolution is enabled by default. The Micronaut framework supports resolving resources from the classpath or the file system.
See the information below for available configuration options:
include::{includedir}configurationProperties/io.micronaut.web.router.resource.StaticResourceConfiguration.adoc[]
TIP: Read the https://guides.micronaut.io/latest/micronaut-static-resources.html[Serving static resources in a Micronaut Application] guide, a step-by-step tutorial, to learn how to expose static resources such as CSS or images in a Micronaut Framework application.
---
### Src Main Docs Guide HttpServer StatusAnnotation (`src/main/docs/guide/httpServer/statusAnnotation.adoc`)
A Micronaut controller action responds with a 200 HTTP status code by default.
If the action returns an `HttpResponse`, configure the status code for the response with the `status` method.
snippet::io.micronaut.docs.server.response.StatusController[tags="httpresponse", indent=0]
You can also use the `@Status` annotation.
snippet::io.micronaut.docs.server.response.StatusController[tags="atstatus", indent=0]
or even respond with an `HttpStatus`
snippet::io.micronaut.docs.server.response.StatusController[tags="httpstatus", indent=0]
=== Custom HTTP Status Codes
Micronaut supports arbitrary HTTP status codes not part of the predefined `HttpStatus` enum.
You can use the API method https://docs.micronaut.io/latest/api/io/micronaut/http/HttpResponse.html#status(int,java.lang.String)[`HttpResponse#status(int,java.lang.String)`]
---
### Src Main Docs Guide HttpServer TrainingRun (`src/main/docs/guide/httpServer/trainingRun.adoc`)
A JDK AOT cache (`-XX:AOTCacheOutput`), a dynamic CDS archive (`-XX:ArchiveClassesAtExit`), a GraalVM profile for profile-guided optimization (`.iprof`) and the `native-image-agent` configuration are all written when the JVM exits after a representative run of the application. A Micronaut application does not exit on its own, so the build would have to start it, wait until it is ready, send it some requests and stop it.
A _training run_ does this from inside the application. It has two modes, which `micronaut.application.training.mode` selects:
* `start`, the default, starts the application as usual, HTTP server included. Once the server has started, it can send warm-up GET requests to it. Then it stops the application and exits.
* `load` starts neither the application nor the server: it reads the configuration, loads the bean definitions without creating any bean and exits. It sends no request. It is described in <>.
Set `micronaut.application.training.enabled` to `true`, for example with `-Dmicronaut.application.training.enabled=true` or the `MICRONAUT_APPLICATION_TRAINING_ENABLED=true` environment variable. In the `start` mode, `Micronaut.run` then:
. logs a warning that this JVM is a training run and does not serve traffic, before the application starts,
. starts the application as usual and waits until startup has completed and every startup event listener has run,
. logs a second warning, stops the application and closes the application context,
. exits the JVM with status 0.
Only `true` turns the switch on, in any case (`TRUE` works too). Any other text, `yes` and `on` included, leaves it off when the value is a string: a system property, an environment variable, a `.properties` file or a quoted YAML value. An unquoted `yes` or `on` in a YAML file is different: YAML reads it as the boolean `true`, so `enabled: yes` in `application.yml` turns the training run on.
A failure during startup, the warm-up included, still exits with a non-zero status, so a failed training run fails the build. This applies to every embedded application, including messaging applications without an HTTP server. In the `test` environment the application is stopped, but `System.exit` is not called.
This is the `start` mode of a training run, the default. It creates the beans of the application, so it needs the services they use. If the application cannot start where the training runs, for example because it needs a database and the training is a step of a container image build, use the `load` mode described in <>.
WARNING: Never set `micronaut.application.training.enabled` or `MICRONAUT_APPLICATION_TRAINING_ENABLED` on a deployment target, whether in the configuration of the deployed application or in an environment variable shared by a platform or a cluster. The application would start, stop right away and exit with status 0, which most orchestrators read as a clean shutdown: the service restarts in a loop and nothing reports an error. The exit status has to stay 0 for the tools that write their output at exit, so the two warnings in the log are the only signal. Set the switch only in the build step that runs the training.
The switch acts on the api:runtime.EmbeddedApplication[] of the application, such as the HTTP server or a messaging application. An application without one is not stopped: `Micronaut.run` logs a warning that says so and returns as usual. The beans that require the switch are still created, so a startup listener written for the training run still runs. If such an application starts non-daemon threads of its own, the training run does not end until they do, so the application has to stop them itself.
To warm up the application before it stops, list the paths the HTTP server should send GET requests to itself, next to the switch. The requests are only sent in the `start` mode, which starts the server; the `load` mode starts no server and ignores these settings:
[configuration]
----
micronaut:
application:
training:
warmup:
paths:
- /hello
- /books?page=1
repeat: 3
----
.Configuration Properties for the training warm-up
|===
|Property |Type |Description |Default value
| `+micronaut.application.training.warmup.paths+`
|java.util.List<java.lang.String>
|The paths the warm-up sends a GET request to, in order, including the context path if the server has one. A path may carry a query string.
|none, so no warm-up requests are sent
| `+micronaut.application.training.warmup.repeat+`
|int
|How many times the warm-up requests the paths. It must be at least 1.
|1
|===
The <<configurationreference#io.micronaut.http.server.TrainingWarmupConfiguration,configuration reference>> lists these properties too.
The warm-up runs once the server has started and listens on its port, after the other api:runtime.server.event.ServerStartupEvent[] listeners, for the server the application runs (not for other servers built with a server factory). It sends each path in order, `repeat` times, with `java.net.HttpURLConnection`, so no HTTP client is needed. `repeat` must be at least 1: a lower value fails the training run, even when no paths are set. The requests go to the configured `micronaut.server.host`, or to the loopback address when no host or a wildcard address such as `0.0.0.0` is set. The paths are sent as they are, so they include the context path if the server has one.
An I/O error or a response status of 500 or above fails the startup. A status from 400 to 499 does not: the warm-up logs a warning with the URL and the status, and the training run continues.
The warm-up runs while the server starts, so the `Startup completed in` time that a training run logs includes the warm-up requests. Do not compare it with the startup time of a normal run. The warm-up logs its own duration: `Training warm-up sent 3 GET requests to http://127.0.0.1:8080 in 45ms`.
For example, the following command trains a JDK AOT cache with a warm-up and exits once the cache is written:
[source,bash]
----
$ java -XX:AOTCacheOutput=app.aot \
-Dmicronaut.application.training.enabled=true \
-Dmicronaut.application.training.warmup.paths=/hello \
-jar app.jar
----
The built-in warm-up only sends plain HTTP GET requests, without credentials:
* If warm-up paths are set and the server has SSL enabled, the training run fails.
* If the application rejects a request, for example with 401 or 403 because the path is secured, or with 429 from a rate limiter, the warm-up logs the warning described above and continues. A rejected request still exercises the server, the routing and the filters that rejected it, but not the code behind them. A path that does not exist (404) is reported the same way, so check the log of a training run for these warnings.
For other requests (a POST, authenticated requests, a server with SSL enabled, or a messaging application), write a startup listener that only exists during a training run:
[source,java]
----
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.runtime.server.event.ServerStartupEvent;
import jakarta.inject.Singleton;
@Singleton
@Requires(property = "micronaut.application.training.enabled", pattern = "(?i)true")
public class CustomWarmup implements ApplicationEventListener<ServerStartupEvent> {
@Override
public void onApplicationEvent(ServerStartupEvent event) {
// send the requests to event.getSource(), throw to fail the training run
}
}
----
The `pattern` matches `true` in any case, as the switch does: with `value = "true"` a training run started with `TRUE` would stop the application without running the listener. For a messaging application, listen for api:runtime.event.ApplicationStartupEvent[] instead.
WARNING: A training run in `start` mode executes application code, including the warm-up requests. If the application needs external services such as a database, run the training with an environment that configures them, or in `load` mode. Micronaut Test Resources cannot replace them: a training run disables it, as described in <>.
[[trainingRunLoad]]
=== Training without starting the application
Set `micronaut.application.training.mode` to `load`, next to the switch, for a training run that does not start the application. The environment variable is `MICRONAUT_APPLICATION_TRAINING_MODE=load`:
[source,bash]
----
$ java -XX:AOTCacheOutput=app.aot \
-Dmicronaut.application.training.enabled=true \
-Dmicronaut.application.training.mode=load \
-jar app.jar
----
`Micronaut.run` then:
. reads the configuration of the application as usual,
. logs a warning that this JVM is a training run and does not serve traffic,
. reads the bean definitions and evaluates their conditions without starting the application context, as `configure()` of api:context.ConfigurableBeanContext[] does,
. loads every enabled bean definition and the classes it names: the bean class, the classes generated for AOP advice, and the types of its constructor, of its injected fields and methods and of its executable methods,
. logs how many bean definitions it loaded, logs a second warning and closes the application context,
. exits the JVM with status 0.
No bean is created and no bean class is initialized. No constructor, `@PostConstruct` method or startup event listener runs, no `@Context` or other eager bean is created, no method is scheduled, the api:runtime.EmbeddedApplication[] is not started and no port is bound. A bean that opens a connection when it is created therefore does not fail the run. A bean definition that cannot be loaded, for example because it names a class of an optional dependency that is absent, is skipped, and so is one with a condition that cannot be evaluated (see below). The run logs each skipped bean definition with the cause, and counts it in the line that ends the loading:
----
Training run (micronaut.application.training.mode=load): loaded 359 of 411 bean definitions and the 655 types they name in 43ms, skipped 0 that could not be evaluated or loaded
----
The mode is read only when `micronaut.application.training.enabled` is `true`. Its values are `start`, the default, and `load`, in any case; any other value fails the training run. The `load` mode works for every application, including one without an `EmbeddedApplication`, which a `start` run cannot stop. In the `test` environment `System.exit` is not called and `Micronaut.run` returns the application context, closed and never started.
The warm-up does not apply to the `load` mode, because no server is started. If `micronaut.application.training.warmup` settings are present, a warning says that they are ignored.
What still runs in `load` mode is the code that runs whenever an application context is configured:
* the property source loaders. The one of Micronaut Test Resources supplies nothing, because a training run disables it (see <>). If the application reads distributed configuration at startup through the bootstrap context, it still does, and the beans of the bootstrap context that read it are created.
* the api:context.ApplicationContextConfigurer[] classes of the application.
* the conditions of the bean definitions, custom api:context.condition.Condition[] classes included. A condition cannot look up a bean: the application context is not running, so the lookup throws an `IllegalStateException` and no bean is created. The bean definition with that condition is skipped and logged with that cause, although a normal start evaluates the condition.
==== Choosing a mode
The tools that a training run feeds record what the JVM loads and what it executes. A `load` run loads the classes of the beans and executes the framework code that builds the application context, but none of the code that creates the beans, starts the server and handles requests. A JDK AOT cache or a CDS archive trained in `load` mode therefore holds fewer classes, and none of the linked lambdas and method profiles of that code: the application starts faster than without a cache, but slower than with a cache trained in `start` mode.
|===
|Training run |When to use it
|`start` with warm-up paths
|The application can start where the training runs: the services it needs are available, or a training environment replaces them. This gives the most complete cache.
|`start` without warm-up paths
|As above, when the application has no request that is safe to send.
|`load`
|The application cannot start where the training runs. The cache is smaller and keeps only part of the gain.
|===
For a GraalVM profile for profile-guided optimization or for the `native-image-agent`, use the `start` mode: both record what the application executes, and a `load` run executes almost none of it.
[[trainingRunTestResources]]
=== Test Resources
https://micronaut-projects.github.io/micronaut-test-resources/latest/guide/[Micronaut Test Resources] supplies the properties that the configuration of an application lacks, such as `datasources.default.url`, and starts a container for each one that is read. Left enabled in a training run, it would change what the run trains: a `load` run would ask the Test Resources server which properties it can supply while it reads the configuration, and a condition that compares the value of such a property would make the server start a container; a `start` run would train against the containers. A training run therefore disables Test Resources, in both modes:
* Before it reads the configuration, the training run sets the system property `micronaut.test.resources.enabled` to `false`, the switch of the Test Resources client. The client then contacts no server, supplies no property and starts no container. The system property gets its previous value back once the configuration has been read; the configuration of the training run keeps `false`.
* This needs the training run switch before the configuration is read: set `micronaut.application.training.enabled` as a system property, as the `MICRONAUT_APPLICATION_TRAINING_ENABLED` environment variable or as an argument of the application. If Test Resources is on the class path and the switch is only set in the configuration of the application, for example in a configuration file, the training run fails with a message that says so.
* A Test Resources module that does not read that switch, such as `micronaut-test-resources-embedded`, cannot be disabled: if one is on the class path, the training run fails.
* Test Resources reuses a client that the same JVM has already created from system properties, whatever the switch says, and the training run cannot tell. In practice this is a test that starts an application context with Test Resources and then runs a training run in the same JVM: run such a training run in a JVM of its own.
The Micronaut Gradle plugin adds the Test Resources client to the `developmentOnly` and `testRuntimeOnly` configurations, and a Maven project generated by Micronaut Launch declares it with the `provided` scope. Neither is part of the runtime class path that a JAR or a container image is built from, so a training run in an image build only sees Test Resources if the build adds it to that class path.
---
### Src Main Docs Guide HttpServer Transfers (`src/main/docs/guide/httpServer/transfers.adoc`)
The Micronaut framework supports sending files to the client in a couple of easy ways.
== Sending File Objects
It is possible to return a link:{jdkapi}/java.base/java/io/File.html[File] object from your controller method, and the data will be returned to the client. The `Content-Type` header of file responses is calculated based on the name of the file.
To control either the media type of the file being sent, or to set the file to be downloaded (i.e. using the `Content-Disposition` header), instead construct a api:http.server.types.files.SystemFile[] with the file to use. For example:
.Sending a SystemFile
[source,java]
----
@Get
public SystemFile download() {
File file = ...
return new SystemFile(file).attach("myfile.txt");
// or new SystemFile(file, MediaType.TEXT_HTML_TYPE)
}
----
== Sending an InputStream
For cases where a reference to a `File` object is not possible (for example resources in JAR files), the Micronaut framework supports transferring input streams. To return a stream of data from the controller method, construct a api:http.server.types.files.StreamedFile[].
TIP: The constructor for `StreamedFile` also accepts a `java.net.URL` for your convenience.
.Sending a StreamedFile
[source,java]
----
@Get
public StreamedFile download() {
InputStream inputStream = ...
return new StreamedFile(inputStream, MediaType.TEXT_PLAIN_TYPE)
// An attach(String filename) method is also available to set the Content-Disposition
}
----
The server supports returning `304` (Not Modified) responses if the files being transferred have not changed, and the request contains the appropriate header. In addition, if the client accepts encoded responses, the Micronaut framework encodes the file if appropriate. Encoding happens if the file is text-based and larger than 1KB by default. The threshold at which data is encoded is configurable. See the server configuration reference for details.
WARNING: Avoid using link:{jdkapi}/java.base/java/io/PipedInputStream.html[PipedInputStream] / link:{jdkapi}/java.base/java/io/PipedOutputStream.html[PipedOutputStream] as the backing source for `StreamedFile` or other `InputStream`-based HTTP responses. `PipedInputStream` tracks the liveness of the reader and writer threads and can report a broken pipe when the response is consumed on short-lived threads. This is especially relevant when the `blocking` executor uses virtual threads. Prefer a stable stream source such as a file, URL, or a reactive `Publisher` response. If you must use piped streams, make sure both ends stay bound to long-lived platform threads for the lifetime of the transfer.
== Sending Reactive Streams as File Downloads
Micro