diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java
index 3f27a7ae4b8c26f25d94b34c4c12f4900a9ed1ff..2aaa6c67463527a87a941e0f7928cea3d996b42c 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java
@@ -16,7 +16,7 @@
 package org.springframework.samples.petclinic.api.application;
 
 import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.api.dto.OwnerDetails;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestTemplate;
 
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java
index 1ac20feb6a1f58fdcc341d3ab414133510c0f8b9..19911796b85210f9d3a649f265e3427c8a276ca7 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java
@@ -15,17 +15,19 @@
  */
 package org.springframework.samples.petclinic.api.application;
 
+import java.util.List;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpMethod;
+import org.springframework.samples.petclinic.api.dto.VisitDetails;
+import org.springframework.samples.petclinic.api.dto.Visits;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
 
-import java.util.List;
-import java.util.Map;
 
 import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.joining;
+import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
 
 /**
  * @author Maciej Szarlinski
@@ -36,13 +38,17 @@ public class VisitsServiceClient {
 
     private final RestTemplate loadBalancedRestTemplate;
 
-    public Map<Integer, List<VisitDetails>> getVisitsForPets(final List<Integer> petIds, final int ownerId) {
-        //TODO:  expose batch interface in Visit Service
-        final ParameterizedTypeReference<List<VisitDetails>> responseType = new ParameterizedTypeReference<List<VisitDetails>>() {
-        };
-        return petIds.parallelStream()
-            .flatMap(petId -> loadBalancedRestTemplate.exchange("http://visits-service/owners/{ownerId}/pets/{petId}/visits", HttpMethod.GET, null,
-                responseType, ownerId, petId).getBody().stream())
+    public Map<Integer, List<VisitDetails>> getVisitsForPets(final List<Integer> petIds) {
+        UriComponentsBuilder builder = fromHttpUrl("http://visits-service/pets/visits")
+            .queryParam("petId", joinIds(petIds));
+
+        return loadBalancedRestTemplate.getForObject(builder.toUriString(), Visits.class)
+            .getItems()
+            .stream()
             .collect(groupingBy(VisitDetails::getPetId));
     }
+
+    private String joinIds(List<Integer> petIds) {
+        return petIds.stream().map(Object::toString).collect(joining(","));
+    }
 }
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java
index 349390686d21c420c7b910bb4c92df08631d116f..9d9828c8ca80eed8598a4c66551f5e2def17da5c 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java
@@ -15,19 +15,18 @@
  */
 package org.springframework.samples.petclinic.api.boundary.web;
 
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.samples.petclinic.api.application.CustomersServiceClient;
-import org.springframework.samples.petclinic.api.application.OwnerDetails;
-import org.springframework.samples.petclinic.api.application.VisitDetails;
+import org.springframework.samples.petclinic.api.dto.OwnerDetails;
 import org.springframework.samples.petclinic.api.application.VisitsServiceClient;
+import org.springframework.samples.petclinic.api.dto.VisitDetails;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 
 import static java.util.Collections.emptyList;
 
@@ -45,7 +44,7 @@ public class ApiGatewayController {
     @GetMapping(value = "owners/{ownerId}")
     public OwnerDetails getOwnerDetails(final @PathVariable int ownerId) {
         final OwnerDetails owner = customersServiceClient.getOwner(ownerId);
-        supplyVisits(owner, visitsServiceClient.getVisitsForPets(owner.getPetIds(), ownerId));
+        supplyVisits(owner, visitsServiceClient.getVisitsForPets(owner.getPetIds()));
         return owner;
     }
 
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java
similarity index 92%
rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java
rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java
index ca2be1cdcca1f5691718441ee84092fafb9f31c0..5cea00a88f64294c2db99f81cdf596ecb5ccb3c8 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java
@@ -13,11 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.samples.petclinic.api.application;
+package org.springframework.samples.petclinic.api.dto;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java
similarity index 90%
rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java
rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java
index b2b40d0e4a4496e26a07cfe85d078c6c2c498c26..e9630d07dae2372b3a39abfb649f88e5cb2370fd 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java
@@ -13,10 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.samples.petclinic.api.application;
+package org.springframework.samples.petclinic.api.dto;
 
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java
similarity index 88%
rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java
rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java
index bd284b558c608a7832ad31417d16b9a9721b04fd..63d2b2005318a08ed4740edd81742847b3dd1488 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java
@@ -13,10 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.samples.petclinic.api.application;
+package org.springframework.samples.petclinic.api.dto;
 
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
 /**
  * @author Maciej Szarlinski
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java
similarity index 86%
rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java
rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java
index beefaa3fb8667ddee7733e7dd6f3e574316a359a..29b7c21927030b9c479b71fc81c1dbfda30656c9 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.samples.petclinic.api.application;
+package org.springframework.samples.petclinic.api.dto;
 
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Value;
 
 /**
  * @author Maciej Szarlinski
  */
-@Data
+@Value
 public class VisitDetails {
 
     private int id;
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java
new file mode 100644
index 0000000000000000000000000000000000000000..58708bc91e4d2082cd16ea0a5200f65970c01c59
--- /dev/null
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.samples.petclinic.api.dto;
+
+import java.util.List;
+import lombok.Value;
+
+/**
+ * @author Maciej Szarlinski
+ */
+@Value
+public class Visits {
+    private List<VisitDetails> items;
+}
diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java
index 807c2cd4e6dbfde03721fbfc0f9dfd1e9514e8db..f799b43a0366d029be3f9c66f54cee21abacbf72 100644
--- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java
+++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java
@@ -15,8 +15,8 @@
  */
 package org.springframework.samples.petclinic.visits.model;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import java.util.Date;
-
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
@@ -26,8 +26,9 @@ import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.TemporalType;
 import javax.validation.constraints.Size;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
 
 /**
  * Simple JavaBean domain object representing a visit.
@@ -37,12 +38,16 @@ import com.fasterxml.jackson.annotation.JsonFormat;
  */
 @Entity
 @Table(name = "visits")
+@Builder(builderMethodName = "visit")
+@NoArgsConstructor
+@AllArgsConstructor
 public class Visit {
 
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
 
+    @Builder.Default
     @Column(name = "visit_date")
     @Temporal(TemporalType.TIMESTAMP)
     @JsonFormat(pattern = "yyyy-MM-dd")
diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java
index 15796fff2690feabb865ea22d8f848590a396de4..b4f8689be8219aa164b47666928cb83772c9c1f9 100644
--- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java
+++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java
@@ -33,4 +33,5 @@ public interface VisitRepository extends JpaRepository<Visit, Integer> {
 
     List<Visit> findByPetId(int petId);
 
+    List<Visit> findByPetIdIn(Iterable<Integer> petIds);
 }
diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
index 0d0199fba6d8a8b035c1fe65b117ccfa59587a7f..2ebab12e1b4fbc518d2d085e440d8c7161e4635f 100644
--- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
+++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
@@ -15,16 +15,21 @@
  */
 package org.springframework.samples.petclinic.visits.web;
 
+import java.util.List;
+import javax.validation.Valid;
 import lombok.RequiredArgsConstructor;
+import lombok.Value;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.samples.petclinic.visits.model.Visit;
 import org.springframework.samples.petclinic.visits.model.VisitRepository;
-import org.springframework.web.bind.annotation.*;
-
-import javax.validation.Valid;
-import java.util.List;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 /**
  * @author Juergen Hoeller
@@ -36,13 +41,13 @@ import java.util.List;
 @RestController
 @RequiredArgsConstructor
 @Slf4j
-public class VisitResource {
+class VisitResource {
 
     private final VisitRepository visitRepository;
 
     @PostMapping("owners/*/pets/{petId}/visits")
     @ResponseStatus(HttpStatus.NO_CONTENT)
-    public void create(
+    void create(
         @Valid @RequestBody Visit visit,
         @PathVariable("petId") int petId) {
 
@@ -52,7 +57,18 @@ public class VisitResource {
     }
 
     @GetMapping("owners/*/pets/{petId}/visits")
-    public List<Visit> visits(@PathVariable("petId") int petId) {
+    List<Visit> visits(@PathVariable("petId") int petId) {
         return visitRepository.findByPetId(petId);
     }
+
+    @GetMapping("pets/visits")
+    Visits visitsMultiGet(@RequestParam("petId") List<Integer> petIds) {
+        final List<Visit> byPetIdIn = visitRepository.findByPetIdIn(petIds);
+        return new Visits(byPetIdIn);
+    }
+
+    @Value
+    static class Visits {
+        private final List<Visit> items;
+    }
 }
diff --git a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4724aa81ee3c43ad023e22a291213ee26c58219a
--- /dev/null
+++ b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java
@@ -0,0 +1,61 @@
+package org.springframework.samples.petclinic.visits.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.samples.petclinic.visits.model.VisitRepository;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+
+import static java.util.Arrays.asList;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.samples.petclinic.visits.model.Visit.visit;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(VisitResource.class)
+@ActiveProfiles("test")
+public class VisitResourceTest {
+
+    @Autowired
+    MockMvc mvc;
+
+    @MockBean
+    VisitRepository visitRepository;
+
+    @Test
+    public void shouldFetchVisits() throws Exception {
+        given(visitRepository.findByPetIdIn(asList(111, 222)))
+            .willReturn(
+                asList(
+                    visit()
+                        .id(1)
+                        .petId(111)
+                        .build(),
+                    visit()
+                        .id(2)
+                        .petId(222)
+                        .build(),
+                    visit()
+                        .id(3)
+                        .petId(222)
+                        .build()
+                )
+            );
+
+        mvc.perform(get("/pets/visits?petId=111,222"))
+            .andExpect(status().isOk())
+            .andExpect(jsonPath("$.items[0].id").value(1))
+            .andExpect(jsonPath("$.items[1].id").value(2))
+            .andExpect(jsonPath("$.items[2].id").value(3))
+            .andExpect(jsonPath("$.items[0].petId").value(111))
+            .andExpect(jsonPath("$.items[1].petId").value(222))
+            .andExpect(jsonPath("$.items[2].petId").value(222));
+    }
+}