Kotlin & Spring MVC - HTTP Status 400 - Null - spring

I have a Kotlin #RestController, and I would expect it to return 400 Bad Request in a situation where a null is passed for a #RequestParam argument.
Example:
#RestController
class Api() {
#PostMapping("endpoint")
fun endpoint(#DateTimeFormat(iso = DATE) #RequestParam date: LocalDate) {
//do something
}
}
If I were to make a request to POST /endpoint?date I get a 500 Internal Server Error with the following (shortened) body:
{
"timestamp": "2020-09-14T20:39:38.102+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Parameter specified as non-null is null: method Api.endpoint, parameter date",
"trace": "java.lang.IllegalArgumentException: Parameter specified as non-null is null: method Api.endpoint, parameter date\r\n\tat Api.endpoint(Api.kt)\r\n\t
...
...
atjava.base/java.lang.Thread.run(Thread.java:834)\r\n",
"path": "/campaigns/contributions/unfunded/retries"
}
Is there any way to fix this using either some additional library, a configuration, or some custom code that does not have other side effects that keeps everything the same except that the status code will be 400 Bad Request

The following works for Kotlin 1.4.*. This is not necessarily the best answer because there is no guarantee that Kotlin will not change the excption type of message in future releases. For eaxample, when I originally asked the question I was using 1.3.*, and the exception was IllegalArgumentException. In 1.4.* it has been changed to NullPointerException.
#ControllerAdvice
class KotlinNonNullParamHandling {
#ExceptionHandler
protected fun handleKotlinNonNullViolation(
exception: NullPointerException,
response: HttpServletResponse
) {
val nullParameter = exception.message?.startsWith("Parameter specified as non-null is null: ") == true
val restController = exception
.stackTrace
.getOrNull(0)
?.let { Class.forName(it.className) }
?.getAnnotation(RestController::class.java) != null
val status =
if (nullParameter && restController) HttpStatus.BAD_REQUEST
else HttpStatus.INTERNAL_SERVER_ERROR
response.sendError(status.value(), exception.message)
}
}
Before assuming that it is 400, we are checking that the exception
is a NullPointerException
has a message starting with "Parameter specified as non-null is null: "
it was thrown from a class the is RestController (otherwise it might be some other problem, such as a null being sent to a method by reflection having nothing to do with the web layer.)

Spring will throw the IllegalArgumentException by default when a validation fails here. Every exception inside your Spring-Boot application will be treated by default as InternalServerError.
You can modify your response code by adding a function like this:
#RestController
class Api() {
#ExceptionHandler(IllegalArgumentException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void onIllegalArgumentException(IllegalArgumentException e) {
}
#PostMapping("endpoint")
fun endpoint(#DateTimeFormat(iso = DATE) #RequestParam date: LocalDate) {
//do something
}
}
Afterwards you'll get the 400 status code on invalid request param.

I've tested your code, and there's nothing obviously wrong with it - works as expected.
curl -v -d "date=2020-10-01" http://localhost:8080/endpoint
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /endpoint HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Length: 15
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 15 out of 15 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Length: 0
< Date: Tue, 13 Oct 2020 07:04:21 GMT
<
* Connection #0 to host localhost left intact
So I would:
Verify that the client is properly formatting the request - e.g. try explicitly setting the Content-Type header to application/x-www-form-urlencoded, check if there's no typo in the param name etc.
That you have Jackson properly configured to work with Kotlin (com.fasterxml.jackson.module:jackson-module-kotlin - dependency)

Related

Multipart request rejected because no bounday were found while boundary is already sent

I went through the other similar questions but couldn't find any solution. We have a backend service running by Spring Boot and has been working for a while now. But there is a new user of this service recently and they are using MuleSoft to send their request. But all attempts to send a file to our service fails with this error:
Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found"
The only difference we could find between a request from MuleSoft and say a curl command is that MuleSoft always sends the request with boundary value wrapped with double quotes
Mule request:
<Header name="Content-Type">multipart/form-data; charset=UTF-8; boundary="--------------------------669816398596264398718285"</Header>
Versus Postman/curl request:
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fd656810a00)
> POST /api/upload HTTP/2
> Host: myhost
> user-agent: curl/7.79.1
> accept: */*
> content-length: 97255
> content-type: multipart/form-data; boundary=------------------------111fd08cb4fafe1c
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* We are completely uploaded and fine
< HTTP/2 200
< date: Mon, 19 Dec 2022 04:56:25 GMT
< content-length: 0
Our controller in Spring is very simple:
#RestController
class MyController {
#PostMapping("/upload", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
#ResponseStatus(HttpStatus.OK)
fun uploadDocument(#RequestPart("file") file: MultipartFile) {
logger.info { "ContentType: ${file.contentType}" }
logger.info { "Name: ${file.name}" }
logger.info { "Byte: ${String(file.bytes)}" }
}
}
The following curl command works fine:
curl -v -X POST -F file=#/Users/myhomefolder/Documents/some-file.jpg https://host-name/api/upload
But this script from MuleSoft doesn't (Sorry I'm not familiar with Mule, I got this code from their team):
import dw::module::Multipart
output multipart/form-data boundary = "---WebKitFormBoundary7MA4YWxkTrZu0gW"
---
{
parts : {
file : {
headers : {
"Content-Disposition" : {
"name": "file",
"filename": payload.name
},
"Content-Type" : "multipart/form-data"
},
content : payload.byteArray
}
}
}
Is there any configuration in Spring that accepts double quotes for boundary? Is there anything missing in our backend configuration that should be added to support different HTTP client?

Spring Boot Validation of Path Variables returns blank message

I'm using spring boot validation to validate the #PathVariable as shown in the code below, the validation works however there is no message returned when I execute through curl.exe, I expect to receive the default message.
#GetMapping("/number/{id}")
String getNumber(#PathVariable #Min(2) Long id) {
System.out.println("getNumber :" + id);
return "param id is : " + id;
}
I have specified a CustomGlobalExceptionHandler to catch ConstraintViolationException and this works in that the returned http status code is 400 and it displays a message in the console log "must be greater than or equal to 2"
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
void onConstraintValidationException(ConstraintViolationException e) {
System.out.println("in onConstraintValidationException - start");
String error = "blank";
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
error = violation.getMessage();
System.out.println(error);
}
System.out.println("in onConstraintValidationException - end");
}
I'm using the command line to execute the REST Service
curl.exe -v -X GET "http://localhost:8080/demo-0.0.2-SNAPSHOT/number/1" -H "accept: /"
and I receive this output, but no message
*Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /demo-0.0.2-SNAPSHOT/number/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> accept: */*
>
< HTTP/1.1 400
< Transfer-Encoding: chunked
< Date: Tue, 11 Aug 2020 09:28:06 GMT
< Connection: close
<
* Closing connection 0*
I based this on the mykong.com example https://mkyong.com/spring-boot/spring-rest-validation-example/ section 3. Path Variables Validation. where the results should match
curl -v localhost:8080/books/0
{
"timestamp":"2019-02-20T13:35:59.808+0000",
"status":400,
"error":"Bad Request",
"message":"findOne.id: must be greater than or equal to 1",
"path":"/books/0"
}
Can you suggest why I don't receive a message returned back to the command line?
Did you add these annotations to your controller:
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
#RestController
#Validated
You have to implement this class in order to get 400 message instead of 500:
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
//..
}

#RequestHeader behaviour not as expected; working without required User-Agent

I have a project set up using Spring Boot with Kotlin to make REST APIs.
I'm trying to use the #RequestHeader to recognize the User-Agent. The said header is required=true -
#PostMapping("details", produces = ["application/json"])
fun addInfo(#RequestHeader(name = "User-Agent", required = true) userAgent: String,
#Valid #RequestBody podEntity: PodEntity): ResponseEntity<String> {
pod.addPod(podcastEntity)
return ResponseEntity<String>("{ \"status\":\"Added\" }", HttpStatus.CREATED)
}
Problems -
However, even if I'm not sending the User-Agent header, the API is working and adding data. But, if I change the header to any other non default names, like, user or request-source, the required=true requirement is enforced to and the API does not work. Does it mean that default headers cannot be made mandatory using the required tag?
The other problem is that in the case of custom header, when the required header is missing for the request, the API fails by giving 400 error code but does not throw any exception. I was expecting HttpClientErrorException for my junit test case but on checking the console, I see no exception. Adding #Throws is also not helping. How do enable my function to throw an exception when the required header is missing?
Unit test -
#Test
fun test_getAll_fail_missingHeaders() {
val url = getRootUrl() + "/details/all"
val headers = HttpHeaders()
val request = HttpEntity(pod, headers)
try {
restTemplate!!.postForEntity(url, request, String::class.java)
fail()
} catch (ex: HttpClientErrorException) {
assertEquals(400, ex.rawStatusCode);
assertEquals(true, ex.responseBodyAsString.contains("Missing request header"))
}
}

Can't specify StatusMessage in HttpStatusCodeResult

I have the following Controller method.
public ActionResult Save(IEnumerable<Model> models)
{
try
{
SaveModels(models);
}
catch (ApplicationException ex)
{
return new HttpStatusCodeResult(500, "error");
}
return new EmptyResult();
}
This will always return "Internal Server Error" as HTTP status description, no matter what message I give to the constructor.
Fiddler output:
HTTP/1.1 500 Internal Server Error
Server: ASP.NET Development Server/10.0.0.0
Date: Tue, 12 Apr 2011 12:44:09 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Length: 0
Connection: Close
If I change the Status Code to 501 I get Not Implemented over the wire, same with 200 OK. And if I select a non-existant status code, like 535 it will just return the status code without any description. I can't see that I'm doing anything wrong according to the documentation and examples I've found from other people using this .
Can anyone see what I'm doing wrong?
I just had the same issue and based on #Mikael's comment, I tried deploying my application to an actual IIS server, and yes, you can specify the actual Status Description and it will be returned to the client side.
Why is it different with Cassini, I'm not really sure about.

With Spring Data REST, why is the #Version property becoming an ETag and not included in the representation?

In Spring Data REST (via Spring Boot 1.3.3), when I GET a resource collection of, say, people, the #Version property is not included with the resources:
$curl -v http://localhost:8080/api/people/1
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/people/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.42.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< ETag: "0"
< Last-Modified: Tue, 26 Apr 2016 00:08:12 GMT
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 26 Apr 2016 00:12:56 GMT
<
{
"id" : 1,
"createdDate" : {
"nano" : 351000000,
"epochSecond" : 1461629292
},
"lastModifiedDate" : {
"nano" : 351000000,
"epochSecond" : 1461629292
},
"firstName" : "Bilbo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/people/1"
},
"person" : {
"href" : "http://localhost:8080/api/people/1"
}
}
* Connection #0 to host localhost left intact
by default, or when I configure my Spring Data repository:
#Configuration
public class ApplicationRepositoryConfiguration
extends RepositoryRestMvcConfiguration
{
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config
)
{
config.exposeIdsFor(Person.class);
config.setBasePath("/api/");
}
}
The #Version is the version of the row of data which is incremented on updates, and included in the ETag HTTP Header data when I query a specific resource. Instead of having to invoke a GET on each resource in the collection, I'd prefer getting the #Version in the collection GET so I can write my application to check the #Version value on each resource update without performing the n addition GET round-trips.
Is there a way to include the #Version field in each of the resources a collection GET?
The entity definition looks like this:
#Data #Entity #EntityListeners(AuditingEntityListener.class)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CreatedDate
#Column(nullable=false)
private Instant createdDate;
#LastModifiedDate
#Column(nullable=false)
private Instant lastModifiedDate;
#Version
#JsonProperty
private Long version;
…
}
No, there is not. The ETag is the HTTP equivalent to what's expressed as #Value property in the backend. Spring Data REST turns all backend related properties that have a corresponding mechanism in the HTTP protocol into exactly those: ids become URIs (and shouldn't be part of the payload either), #LastModifiedDate properties become headers, #Version properties, become ETags.
The reason is pretty simple: if you use HTTP, use the protocol means that are available to you to achieve things that are implemented on the data access level. That's one aspect in which Spring Data REST is not simply exposing a database to the web but actually inspects your model and translates model characteristics into protocol specific means.
Long story short: with Spring Data REST you have two options for updates:
Just PUT without an If-Match header — enforces overriding whatever is present on the server as the aggregate gets loaded, incoming data mapped onto it and it written back. You still get optimistic locking applied if another client changed the aggregate in the meantime (although an admittedly very short window). If that's the case you'll see a 409 Conflict.
PUT with an If-Match header - Spring Data REST checks the ETag submitted against the current value of the version property of the aggregate and return a 412 Precondition Failed in case there's a mismatch at that point. In that case clients can lookup the current state of the resource and decide how to proceed. They might just decide to override what's on the server using PUT without an If-Match header.
Similar optimizations can made for GET requests:
GET with If-None-Match (ETag) / If-Modified-Since (with Last-Modified header value) — You'll see a 304 Not Modified in case the resource is still in the same state as before and you thus avoid spending bandwidth for the response.
Plain GET will always return the representation.
I believe not including the version in the body is a mistake, as we often pull a page of resources, so there we can't rely on the ETag. At least it should be configurable which it is not currently.
I simply add another function to the entity that exposes the version, I am on Spring Boot 2.4.4:
// ...
#Version
private Long version;
public Long getCurrentVersion() {
return version;
}
// ...

Resources