In this short tutorial, we’ll learn about RestTemplate exception: java.lang.IllegalArgumentException: Not enough variable values available to expand.

We’ll first start with a quick refresh on Spring’s RestTemplate. Then, we’ll shed light on the main cause of the exception.

Finally, we’re going to explore possible ways to prevent the exception.

Spring RestTemplate

Typically, Spring provides RestTemplate class to perform HTTP requests and call RESTful services.

The class follows the template method pattern and works with the same principle as other Spring Template classes such as JdbcTemplate.

In other words, RestTemplate offers multiple methods (one for each HTTP method) to make the logic of consuming RESTful web services simple.

For example, it provides getForObject() and getForEntity() to do a GET request on the given URL:

    
        RestTemplate restTemplate = new RestTemplate();
        String Url = "http://localhost:8090/rest-api/get";
        ResponseEntity response = restTemplate.getForEntity(Url + "/3", Foo.class);
    

Bear in mind that Spring 5 introduced WebClient as a modern alternative to RestTemplate.

When the exception is thrown?

RestTemplate throws a “Not enough variable values available to expand” exception when we do a GET request on a URL containing curly braces.

For example, sending a GET request to the following REST API endpoint: http://employees.api.com/search?size=20&sort={"name":"desc"}" will cause getForObject() method to fail with:

    
        java.lang.IllegalArgumentException: Not enough variable values available to expand 'name'
    

Why the exception is thrown?

The short answer is that RestTemplate interprets curly braces {“name”:“desc”} as placehoulder for URI path variables.

RestTemplate tries to replace the path variables but since we don’t provide any values, it fails with the message: Not enough variable values available to expand.

As a rule of thumb, we should never pass curly braces in URLs since they are considered unsafe for several reasons.

Practical Example

To demonstrate how this exception is produced and how to prevent it, we’re going to have a look at a practical example.

First, we’re going to build a simple REST API for employee management.

Let’s assume that the API will have only one single endpoint for retrieving a list of employees.

For instance, consider the Employee class:

    
        public class Employee {
            private int id;
            private String firstName;
            private String lastName;
            private double salary;

            public Employee() {
            }
            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            public String getFirstName() {
                return firstName;
            }
            public void setFirstName(String firstName) {
                this.firstName = firstName;
            }
            public String getLastName() {
                return lastName;
            }
            public void setLastName(String lastName) {
                this.lastName = lastName;
            }
            public double getSalary() {
                return salary;
            }
            public void setSalary(double salary) {
                this.salary = salary;
            }
        }
    

Next, let’s create a RESTful Spring controller with a one method to handle our single API’s endpoint:

    
        @RestController
        public class EmployeeApi {
        
            @GetMapping("/get")
            public Employee get(@RequestParam String search) throws JsonMappingException, JsonProcessingException {
                ObjectMapper objectMapper = new ObjectMapper();
                SearchRequest searchRequest = objectMapper.readValue(search, SearchRequest.class);
                
                if(searchRequest.getKey().equals("id")) {
                    return new Employee(Integer.parseInt(searchRequest.getValue()),"John","Martinez",2000);
                }
                else if(searchRequest.getKey().equals("firstName")){
                    return new Employee(2,searchRequest.getValue(),"Johnson",2000);
                }
                else if(searchRequest.getKey().equals("lastName")){
                    return new Employee(3,"Elizabeth",searchRequest.getValue(),2000);
                }
                else if(searchRequest.getKey().equals("salary")){
                    return new Employee(4,"William","Brown",Double.parseDouble(searchRequest.getValue()));
                }
                return new Employee();
            }
        }
    

As we can see, we used ObjectMapper to simplify the logic of converting our query parameter (which is a String object) to an object of the searchRequest class:

    
        public class SearchRequest {
        private String key;
        private String value;

        public SearchRequest() {
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
    

Lastly, let’s put all the pieces together and create a test case to produce the exception:

    
        @Test
        public void whenJsonDataIsPassed_thenProduceException() {
            String url = "http://localhost:8080/api/get?search={\"key\":\"id\",\"value\":4}";
            Employee product = restTemplate.getForObject(url, Employee.class);
        }
    

The test case fails because we passed JSON data as part of the URL. The getForObject method throws IllegalArgumentException with the following message:

    
        java.lang.IllegalArgumentException: Not enough variable values available to expand '"key"'
        at org.springframework.web.util.UriComponents$VarArgsTemplateVariables.getValue(UriComponents.java:369)
        at org.springframework.web.util.HierarchicalUriComponents$QueryUriTemplateVariables.getValue(HierarchicalUriComponents.java:1059)
        ...
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
    

How to Solve the exception?

A basic solution to avoid IllegalArgumentException: Not enough variable values available to expand would be to define a String object containing our search query and provide a real URI variable in the URL.

The following test case is an example on how to solve RestTemplate’s IllegalArgumentException:

    
        @Test
        public void whenUruVariableIsPassed_thenReturnEmployee() {

            String search = "{\"key\":\"firstName\",\"value\":\"Azhwani\"}";
            String url = "http://localhost:8080/get?search={search}";
            Employee employee = restTemplate.getForObject(url, Employee.class, search);

            assertEquals(employee.getLastName(), "Johnson");
        }
    

Conclusion

To sum it up, we talked about Spring RestTemplate and the reason why it throws IllegalArgumentException: “Not enough variables available to expand” exception.

We demonstrated using an example, how to produce the exception and how to prevent it.