- 19.4 Breaking Changes
- Core Changes
- Annotation Inheritance
- Error Response Format
- Runtime Classpath Scanning Removed
- Inject Annotations
- Nullable Annotations
- Server Filter Behavior
- HTTP Compile Time Validation
- Decapitalization Strategy
- @Order default
- Classes Renaming
- Deprecation Removal
- Reflective Bean Map
- Cookie Secure Configuration
- Server Error Route Priority
- Status Route Default Response Status
- No Longer Possible to Inject a
List
ofProvider
- Injecting ExecutorService
- Subclasses Returned From Factories Not Injectable
- No Longer Possible to Define AOP Advice on a Bean Produced from a Factory with Constructor arguments
- Implementations of
javax.inject.Provider
No Longer Generate Factories - Fewer Executable Methods Generated for Controllers and Message Listeners
- GraalVM changes
- Exception Handler Moves
- Module Changes
- Core Changes
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:
Annotation | Inherited |
---|---|
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
❌ | |
✅ | |
@Experimental (source level) | ❌ |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
❌ | |
❌ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
✅ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
✅ | |
✅ | |
✅ | |
❌ | |
✅ | |
❌ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
❌ | |
❌ | |
❌ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
✅ | |
✅ | |
✅ | |
✅ | |
❌ | |
❌ | |
✅ |
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
{
...
"_embedded": {
"errors": [
{
"message": "Person.name: must not be blank"
}
]
}
}
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.
implementation("javax.inject:javax.inject:1")
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</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:
warning: unknown enum constant When.MAYBE
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:
compileOnly("com.google.code.findbugs:jsr305")
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</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.
annotationProcessor("io.micronaut:micronaut-http-validation")
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
</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 toDefaultRouteDataCollector
.RxJavaBeanDefinitionDataCollector.html
has been renamed toDefaultBeanDefinitionDataCollector
.RxJavaHealthAggregator
has been renamed toDefaultHealthAggregator
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.
Cookie Secure Configuration
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:
@Error(status = HttpStatus.UNSUPPORTED_MEDIA_TYPE)
String unsupportedMediaTypeHandler() {
return "not supported";
}
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:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ExecutorService;
import javax.inject.Singleton;
public class ExecutorFactory {
@Singleton
public ExecutorService executorService() {
return ForkJoinPool.commonPool();
}
}
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:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ExecutorService;
import javax.inject.Singleton;
public class ExecutorFactory {
@Singleton
public ForkJoinPool executorService() {
return ForkJoinPool.commonPool();
}
}
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:
import io.micronaut.context.annotation.*;
import io.micronaut.runtime.context.scope.*;
@Factory
class ExampleFactory {
@ThreadLocal
Test test() {
return new Test("foo");
}
}
class Test {
// illegally defines constructor arguments
Test(String name) {}
}
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:
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
public class AProvider implements Provider<A> {
@Override
public A get() {
return new AImpl();
}
}
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:
import io.micronaut.context.annotation.Factory;
import javax.inject.Provider;
import javax.inject.Singleton;
@Factory
public class AProvider implements Provider<A> {
@Override
@Singleton
public A get() {
return new AImpl();
}
}
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.
Old | New |
---|---|
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:
@Nullable
@Value('${greeting}')
protected String before = "Default greeting"
@Nullable
@Value('${greeting:Default greeting}')
protected String after