19.4 Breaking Changes

This section documents breaking changes between Micronaut 2.x and Micronaut 3.x

Core Changes

Annotation Inheritance

Possibly the most important change in Micronaut 3.0 is how annotations are inherited from parent classes, methods and interfaces.

Micronaut 2.x did not respect the rules defined in the AnnotatedElement, and inherited all annotations from parent interfaces and types regardless of the presence of the Inherited annotation.

With Micronaut 3.x and above only annotations that are explicitly meta-annotated with Inherited are now inherited from parent classes and interfaces. This applies to types in the case where one extends another, and methods in the case where one overrides another.

Many of Micronaut’s core annotations have been annotated with @Inherited, so no change will be required, but some annotations that are either outside Micronaut or defined by user code will need changes to code or the annotation.

In general, behaviour which you wish to override is not inherited by default in Micronaut 3.x and above including Bean Scopes, Bean Qualifiers, Bean Conditions, Validation Rules and so on.

The following table summarizes the core Micronaut annotations and which are inherited and which are not:

Table 1. Annotation Inheritance in Micronaut 3.x and above
AnnotationInherited

@Adapter

@Around

@AroundConstruct

@InterceptorBean

@InterceptorBinding

@Introduction

@Blocking

@Creator

@EntryPoint

@Experimental (source level)

@Indexes & @Indexed

@Internal

@Introspected

@NonBlocking

@Nullable

@NonNull

@Order

@ReflectiveAccess

@TypeHint

@SingleResult

@Bindable

@Format

@MapFormat

@ReadableBytes

@Version

@AliasFor

@Any

@Bean

@BootstrapContextCompatible

@ConfigurationBuilder

@ConfigurationInject

@ConfigurationProperties

@ConfigurationReader

@Context

@DefaultImplementation

@DefaultScope

@EachBean

@Executable

@Factory

@NonBinding

@Parallel

@Parameter

@Primary

@Property

@PropertySource

@Prototype

@Replaces

@Requirements

@Requires

@Secondary

@Type

@Value

@Controller

@Body

@Consumes

@CookieValue

@CustomHttpMethod

@Delete

@Error

@Filter

@FilterMatcher

@Get

@Head

@Header

@Headers

@HttpMethodMapping

@Options

@Part

@Patch

@PathVariable

@Post

@Produces

@Put

@QueryValue

@RequestAttribute

@RequestAttributes

@RequestBean

@Status

@Trace

@UriMapping

@Client

@JacksonFeatures

@Delete

@Endpoint

@Read

@Sensitive

@Selector

@Write

@Liveness

@Readiness

@MessageBody

@MessageHeader

@MessageHeaders

@MessageListener

@MessageMapping

@MessageProducer

@SendTo

@CircuitBreaker

@Fallback

@Recoverable

@Retryable

@Refreshable

@ScopedProxy

@ThreadLocal

@EventListener

@RequestScope

@Async

@ExecuteOn

@Scheduled

@SessionValue

@ContinueSpan

@NewSpan

@SpanTag

@Validated

@ClientWebSocket

@OnClose

@OnError

@OnMessage

@OnOpen

@ServerWebSocket

@WebSocketComponent

@WebSocketMapping

When upgrading an application you may need to take action if you implement an interface or subclass a superclass and override a method.

For example the annotations defined in javax.validation are not inherited by default, so they must be defined again in any overridden or implemented methods.

This behaviour grants more flexibility if you need to redefine the validation rules. Note that it is still possible to inherit validation rules through meta-annotations. See the section on Annotation Inheritance for more information.

Error Response Format

The default value of jackson.always-serialize-errors-as-list is now true. That means by default the Hateoas JSON errors will always be a list. For example:

Example error response

  1. {
  2. ...
  3. "_embedded": {
  4. "errors": [
  5. {
  6. "message": "Person.name: must not be blank"
  7. }
  8. ]
  9. }
  10. }

To revert to the previous behavior where a singular error was populated in the message field instead of including _embedded.errors, set the configuration setting to false.

Runtime Classpath Scanning Removed

It is no longer possible to scan the classpath at runtime using the scan method of the Environment interface.

This functionality has not been needed for some time as scanning is implemented at build time through Bean Introspections.

Inject Annotations

Micronaut now provides the jakarta.inject annotations as a transitive dependency instead of the javax.inject annotations. To continue using the old annotations, add the following dependency.

  1. implementation("javax.inject:javax.inject:1")
  1. <dependency>
  2. <groupId>javax.inject</groupId>
  3. <artifactId>javax.inject</artifactId>
  4. <version>1</version>
  5. </dependency>

Nullable Annotations

Micronaut no longer exports any third party dependency for nullability annotations. Micronaut now provides its own annotations for this purpose (Nullable and NonNull) that are used for our APIs. To continue using other nullability annotations, simply add the relevant dependency.

Internally, Micronaut makes use of a third party annotation that may manifest as a warning in your project:

  1. warning: unknown enum constant When.MAYBE
  2. reason: class file for javax.annotation.meta.When not found

This warning is harmless and can be ignored. To eliminate this warning, add the following dependency to your project’s compile only classpath:

  1. compileOnly("com.google.code.findbugs:jsr305")
  1. <dependency>
  2. <groupId>com.google.code.findbugs</groupId>
  3. <artifactId>jsr305</artifactId>
  4. </dependency>

Server Filter Behavior

In Micronaut 2 server filters could have been called multiple times in the case of an exception being thrown, or sometimes not at all if the error resulted before route execution. This also allowed for filters to handle exceptions thrown from routes. Filters have changed in Micronaut 3 to always be called exactly once for each request, under all conditions. Exceptions are no longer propagated to filters and instead the resulting error response is passed through the reactive stream.

In the case of a response being created as a result of an exception, the original cause is now stored as a response attribute (EXCEPTION). That attribute can be read by filters to have context for the error HTTP response.

The OncePerRequestHttpServerFilter class is now deprecated and will be removed in the next major release. The OncePerRequestHttpServerFilter stores a request attribute when the filter is executed and some functionality may rely on that attribute existing. The class will still create the attribute but it is recommended to instead create a custom attribute in your filter class and use that instead of the one created by OncePerRequestHttpServerFilter.

There is also a minor behavior change in when the response gets written. Any modifications to the response after the underlying onNext call is made will not have any effect as the response has already been written.

HTTP Compile Time Validation

Compile time validation of HTTP related classes has been moved to its own module. To continue validating controllers, websocket server classes add http-validation to the annotation processor classpath.

  1. annotationProcessor("io.micronaut:micronaut-http-validation")
  1. <dependency>
  2. <groupId>io.micronaut</groupId>
  3. <artifactId>micronaut-http-validation</artifactId>
  4. </dependency>

Decapitalization Strategy

For many cases, one common one being introspections, getter names like getXForwarded() would result in the bean property being XForwarded. The name will now be xForwarded. This can affect many areas of the framework where names like XForwarded are used.

@Order default

Previously the default order value for the @Order annotation was the lowest precedence. It is now 0.

Classes Renaming

  • RxJavaRouteDataCollector has been renamed to DefaultRouteDataCollector.

  • RxJavaBeanDefinitionDataCollector.html has been renamed to DefaultBeanDefinitionDataCollector.

  • RxJavaHealthAggregator has been renamed to DefaultHealthAggregator

Deprecation Removal

Classes, constructors, etc. that have been deprecated in previous versions of Micronaut have been removed.

Reflective Bean Map

In several places in Micronaut, it is required to get a map representation of your object. In previous versions, a reflection based strategy was used to retrieve that information if the class was not annotated with @Introspected. That functionality has been removed and it is now required to annotate classes with @Introspected that are being used in this way. Any class may be affected if it is passed as an argument or returned from any controller or client, among other use cases.

Previously the secure configuration for cookies was only respected if the request was determined to be sent over https. Due to a number of factors including proxies, HTTPS requests can be presented to the server as if they are HTTP. In those cases the setting was not having any effect. The setting is now respected regardless of the status of the request. If the setting is not set, cookies will be secure if the request is determined to be HTTPS.

Server Error Route Priority

Previously if a route could not be satisfied, or an HttpStatusException was thrown, routes for the relevant HTTP status was searched before routes that handled the specific exception. In Micronaut 3 routes that handle the exception will be searched first, then routes that handle the HTTP status.

Status Route Default Response Status

Status error routes will now default to produce responses with the same HTTP status as specified in the @Error annotation. In previous versions a 200 OK response was created. For example:

  1. @Error(status = HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  2. String unsupportedMediaTypeHandler() {
  3. return "not supported";
  4. }

The above method will result in a response of HTTP status 415 with a body of “not supported”. Previously it would have been a response of HTTP status 200 with a body of “not supported”. To specify the desired response status, either annotate the method with @Status or return an HttpResponse.

No Longer Possible to Inject a List of Provider

In Micronaut 2.x it was possible to inject a List<javax.inject.Provider>, although this was undocumented behaviour. In Micronaut 3.x injecting a list of Provider instances is no longer possible and you should instead use the BeanProvider API which provides stream() and iterator() methods to provide the same functionality.

Injecting ExecutorService

In previous versions of Micronaut it was possible to inject an ExecutorService without any qualifiers and the default Netty event loop group would be injected. Because the event loop should not be used for general purpose use cases, the injection will now fail by default with a non unique bean exception. The injection point should be qualified for which executor service is desired.

Subclasses Returned From Factories Not Injectable

It is no longer possible to inject the internal implementation type from beans produced via factories. The type returned from the factory or any of its super types are able to be injected.

For example:

  1. import java.util.concurrent.ForkJoinPool;
  2. import java.util.concurrent.ExecutorService;
  3. import javax.inject.Singleton;
  4. public class ExecutorFactory {
  5. @Singleton
  6. public ExecutorService executorService() {
  7. return ForkJoinPool.commonPool();
  8. }
  9. }

In the above case, if the ExecutorService had been already been retrieved from the context in previous logic, a call to context.getBean(ForkJoinPool.class) would locate the already created bean. This behaviour was inconsistent because if the bean had not yet been created then this lookup would not work. In Micronaut 3 for consistency this is no longer possible.

You can however restore the behaviour by changing the factory to return the implementation type:

  1. import java.util.concurrent.ForkJoinPool;
  2. import java.util.concurrent.ExecutorService;
  3. import javax.inject.Singleton;
  4. public class ExecutorFactory {
  5. @Singleton
  6. public ForkJoinPool executorService() {
  7. return ForkJoinPool.commonPool();
  8. }
  9. }

No Longer Possible to Define AOP Advice on a Bean Produced from a Factory with Constructor arguments

In previous versions of Micronaut it was possible to define AOP advice to a factory method that returned a class that featured constructor arguments. This could lead to undefined behaviour since the argument of the generated proxy which would be dependency injected by the framework may be different from manually constructed proxy target.

The following definition is now invalid in Micronaut 3 and above and will lead to a compilation error:

  1. import io.micronaut.context.annotation.*;
  2. import io.micronaut.runtime.context.scope.*;
  3. @Factory
  4. class ExampleFactory {
  5. @ThreadLocal
  6. Test test() {
  7. return new Test("foo");
  8. }
  9. }
  10. class Test {
  11. // illegally defines constructor arguments
  12. Test(String name) {}
  13. }

Implementations of javax.inject.Provider No Longer Generate Factories

In Micronaut 2.x if you defined a bean that implemented the javax.inject.Provider interface then the return type of the get method also automatically became a bean.

For example:

  1. import javax.inject.Provider;
  2. import javax.inject.Singleton;
  3. @Singleton
  4. public class AProvider implements Provider<A> {
  5. @Override
  6. public A get() {
  7. return new AImpl();
  8. }
  9. }

In the above example a bean of type A would automatically be exposed by Micronaut. This behaviour is no longer supported and instead the @Factory annotation should be used to express the same behaviour. For example:

  1. import io.micronaut.context.annotation.Factory;
  2. import javax.inject.Provider;
  3. import javax.inject.Singleton;
  4. @Factory
  5. public class AProvider implements Provider<A> {
  6. @Override
  7. @Singleton
  8. public A get() {
  9. return new AImpl();
  10. }
  11. }

Fewer Executable Methods Generated for Controllers and Message Listeners

Previous versions of Micronaut specified the @Executable annotation as a meta-annotation on the @Controller, @Filter and @MessageListener annotations. This resulted in generating executable method all non-private methods of classes annotated with these annotations.

In Micronaut 3.x and above the @Executable has been moved to a meta-annotation of @HttpMethodMapping and @MessageMapping instead to reduce memory consumption and improve efficiency.

If you were relying on the presence of these executable methods you must explicitly annotate methods in your classes with @Executable to restore this behaviour.

GraalVM changes

In previous versions of Micronaut annotating a class with @Introspected automatically added it to the GraalVM reflect-config.json file. The original intended usage of the annotation is to generate Bean Introspection Metadata so Micronaut can instantiate the class and call getters and setters without using reflection.

Starting in Micronaut 3.x the @Introspected annotation doesn’t add the class to the GraalVM reflect-config.json file anymore, because in most of the cases is not really necessary. If you need to declare a class to be accessed by reflection, use the @ReflectiveAccess annotation instead.

Another change is regarding the GraalVM resources created at compile-time. In previous versions of Micronaut adding a dependency on io.micronaut:micronaut-graal triggered the generation of the GraalVM resource-config.json that included all the resources in src/main/resources so they were included in the native image. Starting in Micronaut 3.x that is done in either the Gradle or Maven plugins.

Exception Handler Moves

Two exception handlers that were in micronaut-server-netty have now been moved to micronaut-server since they were not specific to Netty. Their package has also changed as a result.

Table 2. Package changes
OldNew

http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/DuplicateRouteHandler.java

http-server/src/main/java/io/micronaut/http/server/exceptions/DuplicateRouteHandler.java

http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/UnsatisfiedRouteHandler.java

http-server/src/main/java/io/micronaut/http/server/exceptions/UnsatisfiedRouteHandler.java

Module Changes

New package for Micronaut Cassandra

The classes in Micronaut Cassandra have been moved from io.micronaut.configuration.cassandra to io.micronaut.cassandra package.

Micronaut Security

Many of the APIs in the Micronaut Security module have undergone changes. Please see the Micronaut Security documentation for the details.

Groovy changes

In previous version the missing property wouldn’t set the field value to null as it would do for the Java code, in the version 3 it should behave in the same way.

Please refactor to use the default value in the @Value annotation:

  1. @Nullable
  2. @Value('${greeting}')
  3. protected String before = "Default greeting"
  4. @Nullable
  5. @Value('${greeting:Default greeting}')
  6. protected String after