diff --git a/CHANGES.txt b/CHANGES.txt
index 7d2b9175561..c65acf4a2a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -186,6 +186,9 @@ New Features
31. SOLR-1038: Enhance CommonsHttpSolrServer to add docs in batch using an iterator API (Noble Paul via shalin)
+32. SOLR-844: A SolrServer implementation to front-end multiple solr servers and provides load balancing and failover
+ support (Noble Paul, Mark Miller, hossman via shalin)
+
Optimizations
----------------------
diff --git a/src/solrj/org/apache/solr/client/solrj/impl/LBHttpSolrServer.java b/src/solrj/org/apache/solr/client/solrj/impl/LBHttpSolrServer.java
new file mode 100644
index 00000000000..6f098cb3376
--- /dev/null
+++ b/src/solrj/org/apache/solr/client/solrj/impl/LBHttpSolrServer.java
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.solr.client.solrj.impl;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.solr.client.solrj.*;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrException;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * LBHttpSolrServer or "LoadBalanced HttpSolrServer" is just a wrapper to CommonsHttpSolrServer. This is useful when you
+ * have multiple SolrServers and the requests need to be Load Balanced among them. This should NOT be used for
+ * indexing. Also see the wiki page.
+ *
+ * It offers automatic failover when a server goes down and it detects when the server comes back up.
+ *
+ * Load balancing is done using a simple roundrobin on the list of servers.
+ *
+ * If a request to a server fails by an IOException due to a connection timeout or read timeout then the host is taken
+ * off the list of live servers and moved to a 'dead server list' and the request is resent to the next live server.
+ * This process is continued till it tries all the live servers. If atleast one server is alive, the request succeeds,
+ * andif not it fails.
+ *
+ * SolrServer lbHttpSolrServer = new LBHttpSolrServer("http://host1:8080/solr/","http://host2:8080/solr","http://host2:8080/solr");
+ * //or if you wish to pass the HttpClient do as follows
+ * httpClient httpClient = new HttpClient();
+ * SolrServer lbHttpSolrServer = new LBHttpSolrServer(httpClient,"http://host1:8080/solr/","http://host2:8080/solr","http://host2:8080/solr");
+ *
+ * This detects if a dead server comes alive automatically. The check is done in fixed intervals in a dedicated thread.
+ * This interval can be set using {@see #setAliveCheckInterval} , the default is set to one minute.
+ *
+ * When to use this? This can be used as a software load balancer when you do not wish to setup an external
+ * load balancer. The code is relatively new and the API is currently experimental. Alternatives to this code are to use
+ * a dedicated hardware load balancer or using Apache httpd with mod_proxy_balancer as a load balancer. See Load balancing on Wikipedia
+ *
+ * @version $Id$
+ * @since solr 1.4
+ */
+public class LBHttpSolrServer extends SolrServer {
+ private final CopyOnWriteArrayList aliveServers = new CopyOnWriteArrayList();
+ private final CopyOnWriteArrayList zombieServers = new CopyOnWriteArrayList();
+ private ScheduledExecutorService aliveCheckExecutor;
+
+ private HttpClient httpClient;
+ private final AtomicInteger counter = new AtomicInteger(-1);
+
+ private ReentrantLock checkLock = new ReentrantLock();
+ private static final SolrQuery solrQuery = new SolrQuery("*:*");
+
+ static {
+ solrQuery.setRows(0);
+ }
+
+ private static class ServerWrapper {
+ final CommonsHttpSolrServer solrServer;
+
+ // Used only by the thread in aliveCheckExecutor
+ long lastUsed, lastChecked;
+
+ int failedPings = 0;
+
+ public ServerWrapper(CommonsHttpSolrServer solrServer) {
+ this.solrServer = solrServer;
+ }
+
+ public String toString() {
+ return solrServer.getBaseURL();
+ }
+ }
+
+ public LBHttpSolrServer(String... solrServerUrls) throws MalformedURLException {
+ this(new HttpClient(new MultiThreadedHttpConnectionManager()), solrServerUrls);
+ }
+
+ public LBHttpSolrServer(HttpClient httpClient, String... solrServerUrl)
+ throws MalformedURLException {
+ this(httpClient, new BinaryResponseParser(), solrServerUrl);
+ }
+
+ public LBHttpSolrServer(HttpClient httpClient, ResponseParser parser, String... solrServerUrl)
+ throws MalformedURLException {
+ this.httpClient = httpClient;
+ for (String s : solrServerUrl) {
+ aliveServers.add(new ServerWrapper(new CommonsHttpSolrServer(s, httpClient, parser)));
+ }
+ }
+
+ public void addSolrServer(String server) throws MalformedURLException {
+ CommonsHttpSolrServer solrServer = new CommonsHttpSolrServer(server, httpClient);
+ checkLock.lock();
+ try {
+ aliveServers.add(new ServerWrapper(solrServer));
+ } finally {
+ checkLock.unlock();
+ }
+ }
+
+ public String removeSolrServer(String server) {
+ try {
+ server = new URL(server).toExternalForm();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ if (server.endsWith("/")) {
+ server = server.substring(0, server.length() - 1);
+ }
+ this.checkLock.lock();
+ try {
+ for (ServerWrapper serverWrapper : aliveServers) {
+ if (serverWrapper.solrServer.getBaseURL().equals(server)) {
+ aliveServers.remove(serverWrapper);
+ return serverWrapper.solrServer.getBaseURL();
+ }
+ }
+ if (zombieServers.isEmpty()) return null;
+
+ for (ServerWrapper serverWrapper : zombieServers) {
+ if (serverWrapper.solrServer.getBaseURL().equals(server)) {
+ zombieServers.remove(serverWrapper);
+ return serverWrapper.solrServer.getBaseURL();
+ }
+ }
+ } finally {
+ checkLock.unlock();
+ }
+ return null;
+ }
+
+ public void setConnectionTimeout(int timeout) {
+ httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeout);
+ }
+
+ /**
+ * set connectionManagerTimeout on the HttpClient.*
+ */
+ public void setConnectionManagerTimeout(int timeout) {
+ httpClient.getParams().setConnectionManagerTimeout(timeout);
+ }
+
+ /**
+ * set soTimeout (read timeout) on the underlying HttpConnectionManager. This is desirable for queries, but probably
+ * not for indexing.
+ */
+ public void setSoTimeout(int timeout) {
+ httpClient.getParams().setSoTimeout(timeout);
+ }
+
+ /**
+ * Tries to query a live server. If no live servers are found it throws a SolrServerException. If the request failed
+ * due to IOException then the live server is moved to dead pool and the request is retried on another live server if
+ * available. If all live servers are exhausted then a SolrServerException is thrown.
+ *
+ * @param request the SolrRequest.
+ *
+ * @return response
+ *
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ public NamedList