36

I have made a Lambda Function and I want to access it via URL with a help of the API Gateway.

I have set it all up, I have also created an application/json body mapping template in API Gateway looking like this:

{ 
    "input": "$input.params('input')",
}

And then I am triggering HTTP GET request that looks like this:

https://dmquh95ckh.execute-api.eu-west-1.amazonaws.com/prod/OtoTestFunction?input=test

My Java handler class looks like this:

public class LambdaFunctionHandler implements RequestHandler<String, String> {

    @Override
    public String handleRequest(String input, Context context) {
        context.getLogger().log("Input: " + input);
        return "Test completed."+input;
    }
}

And this is the full error message:

{
  "errorMessage": "An error occurred during JSON parsing",
  "errorType": "java.lang.RuntimeException",
  "stackTrace": [],
  "cause": {
    "errorMessage": "com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token\n at [Source: lambdainternal.util.NativeMemoryAsInputStream@68c4039c; line: 1, column: 1]",
    "errorType": "java.io.UncheckedIOException",
    "stackTrace": [],
    "cause": {
      "errorMessage": "Can not deserialize instance of java.lang.String out of START_OBJECT token\n at [Source: lambdainternal.util.NativeMemoryAsInputStream@68c4039c; line: 1, column: 1]",
      "errorType": "com.fasterxml.jackson.databind.JsonMappingException",
      "stackTrace": [
        "com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)",
        "com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:835)",
        "com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:59)",
        "com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:12)",
        "com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1441)",
        "com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1047)"
      ]
    }
  }
}
Ondrej Tokar
  • 4,898
  • 8
  • 53
  • 103
  • Is this happening before your Java function is called, or after? Does your "Input: " log message show up in the logs? – Mark B May 11 '16 at 15:15

7 Answers7

39

It worked for me in all the scenarios when I change the type of input argument from String to Object.

public class LambdaFunctionHandler implements RequestHandler<Object, String> {

  @Override
  public String handleRequest(Object input, Context context) {
    String data= input != null ? input.toString() : "{}";
    context.getLogger().log("Input: " + data);
    return "Test completed."+data;
  }
}

************************** Added on 12 March 2021 ***************************

After working on a couple of Lambda implementations, I realized that the input argument is nothing but a plain string representation of a JSON structure or a Map<String, Object> representation. For the map representation, Key is the name of the attribute, and the value is (1) a String if it is a primitive value or (2) a List if it has multiple values, is another Map<String, Object> or another JSON structure. You can recover the JSON representation with:

    if(input instanceof String)
    {
        String lambdaInputJsonStr = (String)input;
    }
    else if(input instanceof Map)
    {
        String lambdaInputJsonStr = gson.toJson((Map)input);
    }
skvp
  • 1,940
  • 1
  • 20
  • 25
  • 1
    I fell into the same trap of using String when following https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/java-basic/src/main/java/example/HandlerString.java – Big Pumpkin Jan 17 '21 at 06:55
  • 4
    This is almost exactly the problem I was facing, ended up using: implements RequestHandler, String> instead of the example I copied which had a Map – Matt Jan 22 '21 at 19:55
  • Changing the data type from String to Object worked for me as well. Thank you! – Shrinath Jan 25 '21 at 07:40
  • didn't work for me I got "Can not deserialize instance of java.lang.String out of START_OBJECT token" – Abel ANEIROS May 20 '21 at 16:13
  • This works. However, parsing an object of Object class will require a lot of work. I have provided an improvement in a separate answer to use AWS provided Event classes. – Anurag Apr 17 '23 at 14:58
10

This is an error message during Lambda deserialization.

Your API Gateway mapping template is sending a JSON object, but your handler is expecting a String. Either send a raw string from API Gateway, or update your handler to use a POJO corresponding to your template output.

i.e.

public class MyPojo {
   private String input;
   public String getInput() { return input; }
   public void setInput(String input) { this.input = input; }
}

See: http://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html

RyanG
  • 3,973
  • 25
  • 19
  • 6
    "Either send a raw string from API Gateway": how is this done? Which "template output" does the answer refer to? – Harald Feb 26 '18 at 11:33
6

I tried with Object as parameter type as well as Pojo class and it worked in certain scenarios but while making a request from browser with API gateway URL, it failed and gave exact above error. Spent at least 2-3 hours to figure out that correct Signature, which would work in most cases is below. However this is for hello world example, you would obviously customize your input as per your requirement.

public class LambdaFunctionHandler implements RequestHandler<***Map<String,Object>,***  Customer> { 
    @Override
    public Customer handleRequest(***Map<String,Object> input***, Context context) {

    }
}
Vikky
  • 1,123
  • 14
  • 16
1

InputStream should be able to handle any input.

Reference: https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html

InputStream – The event is any JSON type. The runtime passes a byte stream of the document to the handler without modification. You deserialize the input and write output to an output stream.

public class Handler implements RequestHandler<InputStream, String> {
@Override
    public String handleRequest(InputStream event, Context context) {
Eugene
  • 143
  • 10
0

The error message is trying to say that "Since input is a String, I am expecting it to start with a quote ". But I am seeing a { instead". That is why changing input type to Object, Map<String,Object> or MyPojo made the parser happy.

If input should truly be a String, the payload value itself must start with a ". For instance, String payload = "\"string input\"".

Big Pumpkin
  • 3,907
  • 1
  • 27
  • 18
0

As mentioned by others, the error comes up because the JSON request cannot be casted to a String object. You can implement your handler using a custom input parameter class, for example:

    public class ZinusoftLambdaHandler  implements 
     RequestHandler<CustomGetEventInput,String> {
        public String handleRequest(CustomGetEventInput input, Context context) {
        context.getLogger().log("Hello World from ZinusoftLambdaHandler : 
        "+input.getValue());
        return "Response from lambda "+input.getValue();
     }
   }

Your CustomGetEventInput class is just a simple POJO class.

In order to invoke a POST method in the API, you need to create a Model and add it to the request body in the Method Request section of the API definition. The json definition of the model needs to match the definition of the CustomGetEventInput class.

For example

public class CustomEventInput {
    private List<Integer> values;

    public CustomEventInput() {     
    }
    
    public CustomEventInput(List<Integer> input) {
        values = input;
    }
    
    public List<Integer> getValues() {
        return values;
    }

    public void setValues(List<Integer> values) {
        this.values = values;
    }
}

JSON Model

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "CustomEventInput",
  "type": "object",
  "properties": {
    "values": {
      "type": "array",
      "items": { "type": "integer" }
    }
  }
}

For a GET method you need to define a URL Query String with the name of the parameters that you are passing to the lambda function.

0

Instead of using Object class, we can use AWS provided event classes like APIGatewayProxyRequestEvent, S3Event, SNSEvent etc. This will save a lot of effort when you need to look into event attributes like api query parameters, api method etc.

You will have to include the following maven dependency:

    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-lambda-java-events</artifactId>
        <version>3.11.1</version>
    </dependency>
Anurag
  • 876
  • 12
  • 16