micronaut-core
micronaut-core copied to clipboard
Controller method with both @Body and HttpRequest arguments fail
Expected Behavior
In micronaut 3 I was using controller methods with both @body
and HttpRequest
as arguments. For example the following code worked fine:
@Controller("/echo")
public class EchoController {
@Post
@Produces(MediaType.TEXT_PLAIN)
public String echo(HttpRequest<String> request, @Body String body) {
return body;
}
}
Actual Behaviour
After upgrading to Micronaut 4 requesting an endpoint that defines both HttpRequest and @Body as arguments throws Java.lang.IllegalStateException: Already claimed
.
Either argument alone works fine. Therefor, as a workaround, I can instead of the @body argument use HttpRequest.getBody().
Steps To Reproduce
No response
Environment Information
No response
Example Application
https://github.com/catatafishen/controller.arguments.example/blob/master/src/main/java/example/micronaut/EchoController.java
Version
4.0.2
We have the same problem - any news on this?
you can also pass a HttpRequest<?>
as a workaround
@yawkat : Thanks a lot 🙂
Will this workaround have an effect on performance?
It seems a bit odd, that it works differently for <?> than specifying an actual type for the generic...
HttpRequest<?> should technically be faster. but i doubt it matters.
We have the same issue, version 4.1.1
Same with version 4.1.2, but as a workaround exists, it's ok for now.
same here.
Also, it looks like @QueryValue
is no longer working with form data like it was in 3.x
I'm seeing the same Already claimed
error when using @Body
& Authentication
like:
@Controller
public class ExampleController {
@Post("/example")
@Produces(MediaType.TEXT_PLAIN)
public String examplePost(@Nullable Authentication auth, @Body String body) {
return body;
}
}
Same error here, we're using as params for the same method @Body
, @Path
and @QueryVaue
annotated parameters and we're hitting the same Already claimed
error.
@Post(uri = "/trigger/{namespace}/{id}", consumes = MediaType.MULTIPART_FORM_DATA)
public Execution trigger(
@PathVariable String namespace,
@PathVariable String id,
@Nullable @Body Map<String, Object> inputs,
@Nullable @QueryValue List<String> labels,
@Nullable @Part Publisher<StreamingFileUpload> files,
@QueryValue(defaultValue = "false") Boolean wait,
@QueryValue Optional<Integer> revision
) {
}
the issue is probably the present of both @Part
and @Body
in your example.
Hi,
Back to this issue, I used the trick to bind the body to Publisher<StreamingFileUpload> and HttpRequest<?>
but it caused a memory issue.
First, let me explain what we do. We have a multipart which can contain plain string or files, the files can have multiple parts with different filenames.
In Micronaut 3, we bind the body to a @Body HashMap<String, Object>
and the files part using an @Part
, and it works fine.
Now that we change the body to an HttpRequest<?>
, I discover that when I do request.getBody(Map.class)
I have all the parts including the files
, so when there is a huge file it is loaded in memory.
So my plan will be to bind the body once in a @Body MultipartBody
but looking at it quickly I cannot access the filename nor have an easy way to know if the part is a file or not (I need to instanceof on internal classes). I'll open an issue for proposing improvements to the MultiPart body to offer in the API the filename and a way to differentiate between a file and an attribute.
I'm seeing the same
Already claimed
error when using@Body
&Authentication
like:@Controller public class ExampleController { @Post("/example") @Produces(MediaType.TEXT_PLAIN) public String examplePost(@Nullable Authentication auth, @Body String body) { return body; } }
I have exact same issue at the moment. I cannot bind both @Body
and Authentication
, the application runtime works fine, but I am not able to test it at all.
@CezaryBD This sounds like you're missing something to bind Authentication
in your tests
@yawkat I face the same issue as @CezaryBD with both Authentication
and Body
in the test
For Get requests without the body (query or path params and Authentication
as parameters for controller method) everything works good, but for requests with body I observe this error
So binding of Authentication is not a problem, cause it works in other cases (especially since in my code and in @CezaryBD Authentication
example marked as @Nullable
)
Same is for case when I have HttpRequest
and Authentication
as parameters
@KazimirDobrzhinsky provide an example that reproduces it
@KazimirDobrzhinsky provide an example that reproduces it
I couldn't provide a full example repository, but here is my dependencies configuration and controller similar to the one, that I am using
`
<properties>
<dir.interface.in>${project.basedir}/src/main/resources/interfaces/in</dir.interface.in>
<dir.interface.out>${project.basedir}/src/main/resources/interfaces/out</dir.interface.out>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<packaging>jar</packaging>
<jdk.version>21</jdk.version>
<release.version>21</release.version>
<micronaut.version>4.5.0</micronaut.version>
<micronaut.runtime>netty</micronaut.runtime>
<micronaut.aot.enabled>false</micronaut.aot.enabled>
<micronaut.aot.packageName>de.telekom.aot.generated</micronaut.aot.packageName>
<exec.mainClass>"deleted"</exec.mainClass>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<allure.version>2.25.0</allure.version>
<lombok.version>1.18.32</lombok.version>
<jacoco.version>0.8.12</jacoco.version>
<awaitility.version>4.2.1</awaitility.version>
<logstash-logback-encoder.version>7.4</logstash-logback-encoder.version>
<elk.apm-agent-attach.version>1.50.0</elk.apm-agent-attach.version>
<oracle.driver-non-reactive.version>23.4.0.24.05</oracle.driver-non-reactive.version>
<wiremock.version>3.6.0</wiremock.version>
</properties>
<dependencies>
<!-- general micronaut dependencies -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-core-processor</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.reactor</groupId>
<artifactId>micronaut-reactor</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.reactor</groupId>
<artifactId>micronaut-reactor-http-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.serde</groupId>
<artifactId>micronaut-serde-jackson</artifactId> <!-- serialisation library -->
<scope>compile</scope>
</dependency>
<!-- security related -->
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security-jwt</artifactId> <!-- incoming security -->
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security-oauth2</artifactId> <!-- outgoing security -->
<scope>compile</scope>
</dependency>
<!-- DB related -->
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-hibernate-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-hibernate-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.beanvalidation</groupId>
<artifactId>micronaut-hibernate-validator</artifactId>
<scope>compile</scope>
</dependency>
<!-- Flyway related -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-oracle-client</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.flyway</groupId>
<artifactId>micronaut-flyway</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-oracle</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>${oracle.driver-non-reactive.version}</version>
</dependency>
<dependency>
<groupId>io.micronaut.sql</groupId>
<artifactId>micronaut-jdbc-hikari</artifactId>
<scope>compile</scope>
</dependency>
<!-- miscellaneous -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <!-- snakeyaml is required to support yaml configuration files -->
<scope>runtime</scope>
</dependency>
<!-- ELK related -->
<dependency>
<groupId>co.elastic.apm</groupId>
<artifactId>apm-agent-attach</artifactId>
<version>${elk.apm-agent-attach.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-logback-encoder.version}</version>
</dependency>
<!-- test related -->
<dependency>
<groupId>io.micronaut.test</groupId>
<artifactId>micronaut-test-rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-rest-assured</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micronaut.test</groupId>
<artifactId>micronaut-test-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-xe</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-bom</artifactId>
<version>${allure.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>io.micronaut.maven</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
<configuration>
<configFile>aot-${packaging}.properties</configFile>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<!-- Uncomment to enable incremental compilation -->
<!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->
<annotationProcessorPaths combine.self="override">
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.core.version}</version>
</path>
<path>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-processor</artifactId>
<version>${micronaut.data.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-graal</artifactId>
<version>${micronaut.core.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
<version>${micronaut.core.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
<version>${micronaut.core.version}</version>
</path>
<path>
<groupId>io.micronaut.serde</groupId>
<artifactId>micronaut-serde-processor</artifactId>
<version>${micronaut.serialization.version}</version>
<exclusions>
<exclusion>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject</artifactId>
</exclusion>
</exclusions>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group=de.telekom</arg>
<arg>-Amicronaut.processing.module=assurancesubscribers</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<executions>
<execution>
<id>sonar</id>
<configuration>
<argLine>@{argLine} -Dfile.encoding=UTF-8</argLine>
</configuration>
</execution>
<execution>
<id>AllureReport-SystemTests</id>
<configuration>
<testFailureIgnore>false</testFailureIgnore>
<argLine>
-javaagent:"${settings.localRepository}${file.separator}org${file.separator}aspectj${file.separator}aspectjweaver${file.separator}${aspectj.version}${file.separator}aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>`
Controller: `import io.micronaut.http.HttpRequest; import io.micronaut.http.annotation.; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.format.Format; import io.micronaut.security.authentication.Authentication; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; import reactor.core.publisher.Mono; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.exceptions.HttpStatusException; import jakarta.annotation.Generated; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import jakarta.validation.Valid; import jakarta.validation.constraints.; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@Controller("/v2") @Tag(name = "", description = "") public class ControllerClass { @Operation( operationId = "", summary = "", responses = { @ApiResponse(responseCode = "201", description = "Created successfully", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = "deleted".class)) }), @ApiResponse(responseCode = "400", description = "Bad request or business logic error occurred."), @ApiResponse(responseCode = "401", description = "User is not logged in i.e. unauthorized"), @ApiResponse(responseCode = "403", description = "The user is not authorized to access the service ."), @ApiResponse(responseCode = "404", description = " was not found."), @ApiResponse(responseCode = "409", description = "A conflict occurred, because two clients tried to create the same resource."), @ApiResponse(responseCode = "415", description = "Unknown media-type received."), @ApiResponse(responseCode = "500", description = "Internal Server Error"), @ApiResponse(responseCode = "501", description = "Not yet implemented"), @ApiResponse(responseCode = "503", description = "Temporarily Unavailable") }, parameters = { @Parameter(name = "_body", description = ".") } ) @Post(uri="/create") @Produces(value = {"application/json"}) @Consumes(value = {"application/json"}) @Secured({SecurityRule.IS_ANONYMOUS}) public Mono<HttpResponse<"deleted">> create( HttpRequest< "deleted"> _body, @Nullable Authentication authentication ) { // imolementation here } @Operation( operationId = "", summary = "", responses = { @ApiResponse(responseCode = "200", description = "OK", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = "deleted".class)) }), @ApiResponse(responseCode = "400", description = "Bad request or business logic error occurred."), @ApiResponse(responseCode = "401", description = "User is not logged in i.e. unauthorized"), @ApiResponse(responseCode = "403", description = "The user is not authorized to access the service ."), @ApiResponse(responseCode = "404", description = " was not found."), @ApiResponse(responseCode = "415", description = "Unknown media-type received."), @ApiResponse(responseCode = "500", description = "Internal Server Error"), @ApiResponse(responseCode = "503", description = "Temporarily Unavailable") }, parameters = { @Parameter(name = "deleted", description = ".", required = true) } ) @Get(uri="/get}") @Produces(value = {"application/json"}) @Secured({SecurityRule.IS_ANONYMOUS}) public Mono<HttpResponse<"deleted">> get( @PathVariable(value="deleted") @NotNull @Min(1L) Long assuranceSubscriptionId, @Nullable Authentication authentication ) { // Timplementation here }
}`