diff --git a/README.md b/README.md
index 05f07b5aaf68ca7067bff93e0d8a0489fda8241e..fd62749022aa57329280640258749bbc40ff4782 100644
--- a/README.md
+++ b/README.md
@@ -709,6 +709,28 @@ You can replace `city` with `country` in any of the above tasks, generators, and
 * **Limitations**: Only good for non-commercial use. For commercial usage please check http://developer.baidu.com/map/question.htm#qa0013
 * **Notes**: To use Baidu set `Geocoder.configure(:lookup => :baidu_ip, :api_key => "your_api_key")`.
 
+#### GeoLite2 (`:geolite2`) - EXPERIMENTAL
+
+This lookup provides methods for geocoding IP addresses without making a call to a remote API (improves speed and availability). It works, but support is new and should not be considered production-ready. Please [report any bugs](https://github.com/alexreisner/geocoder/issues) you encounter.
+
+* **API key**: none (requires the free GeoLite2 City or Country MaxMind DB binary database which can be downloaded from [MaxMind](http://dev.maxmind.com/geoip/geoip2/geolite2/))
+* **Quota**: none
+* **Region**: world
+* **SSL support**: N/A
+* **Languages**: English
+* **Documentation**: http://www.maxmind.com/en/city
+* **Terms of Service**: ?
+* **Limitations**: ?
+* **Notes**: **You must download a database from MaxMind and set the `:file` configuration option for local lookups to work.** The GeoLite2 CSV format is not yet supported since it is still in alpha stage.
+
+**To use the binary database** you must add either the *[hive_geoip2](https://rubygems.org/gems/hive_geoip2)* gem (native extension that relies on libmaxminddb) or the *[maxminddb](http://rubygems.org/gems/maxminddb)* gem (pure Ruby implementation) to your Gemfile or have it installed in your system.
+Then specify which gem to use with the `:maxminddb_gem` configuration option, and specify the path of the MaxMind database in your configuration. The pure Ruby gem (maxminddb) will be used as default. For example to use the maxminddb gem:
+
+    Geocoder.configure(ip_lookup: :geolite2, geolite2: { file: File.join('folder', 'GeoLite2-City.mmdb') })
+
+To use the hive_geoip2 gem:
+
+    Geocoder.configure(ip_lookup: :geolite2, geolite2: { maxminddb_gem: 'hive_geoip2', file: File.join('folder', 'GeoLite2-City.mmdb') })
 
 Caching
 -------
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index 1ec88606595435af439f5dd6361b02573df6dbbf..3a6e7a782b8bba25bdf4f4516dc99d4bd3f9a3f8 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -52,6 +52,7 @@ module Geocoder
       [
         :baidu_ip,
         :freegeoip,
+        :geolite2,
         :maxmind,
         :maxmind_local,
         :telize,
diff --git a/lib/geocoder/lookups/geolite2.rb b/lib/geocoder/lookups/geolite2.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5733d2be0fcb6954206659458c875c7cc4b7422a
--- /dev/null
+++ b/lib/geocoder/lookups/geolite2.rb
@@ -0,0 +1,40 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/geolite2'
+
+module Geocoder
+  module Lookup
+    class Geolite2 < Base
+      def initialize
+        unless configuration[:file].nil?
+          begin
+            @gem_name = configuration[:maxminddb_gem] || 'maxminddb'
+            require @gem_name
+          rescue LoadError
+            raise "Could not load Maxmind DB dependency. To use GeoLite2 lookup you must add the #{@gem_name} gem to your Gemfile or have it installed in your system."
+          end
+        end
+        super
+      end
+
+      def name
+        'GeoLite2'
+      end
+
+      def required_api_key_parts
+        []
+      end
+
+      private
+
+      def results(query)
+        return [] unless configuration[:file]
+        if @gem_name == 'hive_geoip2'
+          result = Hive::GeoIP2.lookup(query.to_s, configuration[:file].to_s)
+        else
+          result = MaxMindDB.new(configuration[:file].to_s).lookup(query.to_s)
+        end
+        result.nil? ? [] : [result]
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/geolite2.rb b/lib/geocoder/results/geolite2.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3f3d027ce468b06b885556073fe5f2723f858082
--- /dev/null
+++ b/lib/geocoder/results/geolite2.rb
@@ -0,0 +1,64 @@
+require 'geocoder/results/base'
+
+module Geocoder
+  module Result
+    class Geolite2 < Base
+      def address(format = :full)
+        s = state.to_s == '' ? '' : ", #{state_code}"
+        "#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, '')
+      end
+
+      def coordinates
+        [latitude, longitude]
+      end
+
+      def latitude
+        return 0.0 unless @data['location']
+        @data['location']['latitude'].to_f
+      end
+
+      def longitude
+        return 0.0 unless @data['location']
+        @data['location']['longitude'].to_f
+      end
+
+      def city
+        return '' unless @data['city']
+        @data['city']['names']['en']
+      end
+
+      def state
+        return '' unless @data['subdivisions']
+        @data['subdivisions'][0]['names']['en']
+      end
+
+      def state_code
+        return '' unless @data['subdivisions']
+        @data['subdivisions'][0]['iso_code']
+      end
+
+      def country
+        @data['country']['names']['en']
+      end
+
+      def country_code
+        @data['country']['iso_code']
+      end
+
+      def postal_code
+        return '' unless @data['postal']
+        @data['postal']['code']
+      end
+
+      def self.response_attributes
+        %w[ip]
+      end
+
+      response_attributes.each do |a|
+        define_method a do
+          @data[a]
+        end
+      end
+    end
+  end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index b6a68f76e57241cb9d1cb7af832f6c192d0fbc4b..b447cf21b097383e5d626d597e688d262b1ac533 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -150,6 +150,25 @@ module Geocoder
       end
     end
 
+    class Geolite2
+      private
+
+      remove_method(:results)
+
+      def results(query)
+        return [] if query.to_s == 'no results'
+        return [] if query.to_s == '127.0.0.1'
+        [{'city'=>{'names'=>{'en'=>'Mountain View'}},'country'=>{'iso_code'=>'US','names'=>
+        {'en'=>'United States'}},'location'=>{'latitude'=>37.41919999999999,
+        'longitude'=>-122.0574},'postal'=>{'code'=>'94043'},'subdivisions'=>[{
+        'iso_code'=>'CA','names'=>{'en'=>'California'}}]}]
+      end
+
+      def default_fixture_filename
+        'geolite2_74_200_247_59'
+      end
+    end
+
     class Telize
       private
       def default_fixture_filename
diff --git a/test/unit/cache_test.rb b/test/unit/cache_test.rb
index 7777fdb2408899ee5fb09295977ed20ac9943a84..6ad4a11e6457beae4b317a1c7e74a692b807bce4 100644
--- a/test/unit/cache_test.rb
+++ b/test/unit/cache_test.rb
@@ -7,7 +7,7 @@ class CacheTest < GeocoderTestCase
   def test_second_occurrence_of_request_is_cache_hit
     Geocoder.configure(:cache => {})
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if l == :maxmind_local # local, does not use cache
+      next if l == :maxmind_local || l == :geolite2 # local, does not use cache
       Geocoder.configure(:lookup => l)
       set_api_key!(l)
       results = Geocoder.search("Madison Square Garden")
diff --git a/test/unit/error_handling_test.rb b/test/unit/error_handling_test.rb
index 29f2ccbb1d9878c107bd64b49a942fdc9c849785..63d6346159fa5b0374107ed5b365371fddbf7158 100644
--- a/test/unit/error_handling_test.rb
+++ b/test/unit/error_handling_test.rb
@@ -21,7 +21,7 @@ class ErrorHandlingTest < GeocoderTestCase
   def test_always_raise_timeout_error
     Geocoder.configure(:always_raise => [TimeoutError])
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if l == :maxmind_local # local, does not raise timeout
+      next if l == :maxmind_local || l == :geolite2 # local, does not use cache
       lookup = Geocoder::Lookup.get(l)
       set_api_key!(l)
       assert_raises TimeoutError do
@@ -33,7 +33,7 @@ class ErrorHandlingTest < GeocoderTestCase
   def test_always_raise_socket_error
     Geocoder.configure(:always_raise => [SocketError])
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if l == :maxmind_local # local, does not raise timeout
+      next if l == :maxmind_local || l == :geolite2 # local, does not use cache
       lookup = Geocoder::Lookup.get(l)
       set_api_key!(l)
       assert_raises SocketError do
@@ -45,7 +45,7 @@ class ErrorHandlingTest < GeocoderTestCase
   def test_always_raise_connection_refused_error
     Geocoder.configure(:always_raise => [Errno::ECONNREFUSED])
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if l == :maxmind_local # local, does not raise timeout
+      next if l == :maxmind_local || l == :geolite2 # local, does not use cache
       lookup = Geocoder::Lookup.get(l)
       set_api_key!(l)
       assert_raises Errno::ECONNREFUSED do
diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb
index b1b30cb90e71d856b55b94d2aa62dc3e55fcd39b..4b36713f94432427a8d9cad3ca5699181077589e 100644
--- a/test/unit/lookup_test.rb
+++ b/test/unit/lookup_test.rb
@@ -23,7 +23,7 @@ class LookupTest < GeocoderTestCase
 
   def test_query_url_contains_values_in_params_hash
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if [:freegeoip, :maxmind_local, :telize, :pointpin].include? l # does not use query string
+      next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geolite2].include? l # does not use query string
       set_api_key!(l)
       url = Geocoder::Lookup.get(l).query_url(Geocoder::Query.new(
         "test", :params => {:one_in_the_hand => "two in the bush"}
diff --git a/test/unit/lookups/geolite2_test.rb b/test/unit/lookups/geolite2_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac9267aaaa3302d95c92c8c3316222fd4a8f3ef7
--- /dev/null
+++ b/test/unit/lookups/geolite2_test.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+require 'test_helper'
+
+class Geolite2Test < GeocoderTestCase
+  def setup
+    Geocoder.configure(ip_lookup: :geolite2, file: 'test_file')
+  end
+
+  def test_result_attributes
+    result = Geocoder.search('8.8.8.8').first
+    assert_equal 'Mountain View, CA 94043, United States', result.address
+    assert_equal 'Mountain View', result.city
+    assert_equal 'CA', result.state_code
+    assert_equal 'California', result.state
+    assert_equal 'United States', result.country
+    assert_equal 'US', result.country_code
+    assert_equal '94043', result.postal_code
+    assert_equal 37.41919999999999, result.latitude
+    assert_equal -122.0574, result.longitude
+    assert_equal [37.41919999999999, -122.0574], result.coordinates
+  end
+
+  def test_loopback
+    results = Geocoder.search('127.0.0.1')
+    assert_equal [], results
+  end
+end