diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7918e00f712f06efda46edec128e46c1e1ff09c9..6ba016365a0c19353050f156b68c0bdeb24a6801 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,3 +15,4 @@ include:
   - local: "/string-calculator-2/.gitlab-ci.yml"
   - local: "/trip-service-kata/.gitlab-ci.yml"
   - local: "/tcr-roman-numerals/.gitlab-ci.yml"
+  - local: "/java-h2g2/.gitlab-ci.yml"
diff --git a/README.md b/README.md
index 1604f63c0436cfc525eafea9a2f8566f9ee23a60..7604eb69cd8c5cd5510df2930b946346299a0ed2 100644
--- a/README.md
+++ b/README.md
@@ -37,3 +37,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré
 | [Proxifier son localhost avec un nginx dockerisé avec Edouard CATTEZ](https://www.youtube.com/watch?v=qWMfRb3zK7k)    | Talk        | Facile     |
 | [Dev pour des data scientists, un sale boulot](https://www.youtube.com/watch?v=QK3OJGAresE)                           | Discussion  |            |
 | [TCR - Roman Numerals](/tcr-roman-numerals)                                                                           | Kata        | Moyenne    |
+| [H2G2 en Java](/java-h2g2)                                                                                            | Kata        | Moyenne    |
diff --git a/java-h2g2/.gitlab-ci.yml b/java-h2g2/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c518f3687485f028ef893255199790d33e3cba9d
--- /dev/null
+++ b/java-h2g2/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+package-java-h2g2:
+  variables:
+    PROJECT_FOLDER: "java-h2g2"
+  extends: .java
+  only:
+    refs:
+      - master
+      - merge_requests
+    changes:
+      - ".gitlab-common-ci.yml"
+      - "java-h2g2/**/*"
diff --git a/java-h2g2/README.md b/java-h2g2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..53fd5db54b7262a563882e71d396107c25c846d4
--- /dev/null
+++ b/java-h2g2/README.md
@@ -0,0 +1,9 @@
+# Java h2g2
+
+Résolution en TDD et en Java du kata [Potter](https://codingdojo.org/kata/Potter/) renommé h2g2 pour l'occasion.
+
+-   **Auteurs** : Anthony REY et Colin DAMON
+-   **Date** : 27/11/2020
+-   **Langage** : Java
+-   **Niveau** : Moyen
+-   **Replay** : TODO
diff --git a/java-h2g2/pom.xml b/java-h2g2/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b9bbe10929cec2b176cfdd2229a80e3e1aebdfea
--- /dev/null
+++ b/java-h2g2/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <version>1.0.0</version>
+    <groupId>fr.ippon.kata</groupId>
+    <artifactId>java-parent</artifactId>
+    <relativePath>../java-parent</relativePath>
+  </parent>
+
+  <version>1.0.0-SNAPSHOT</version>
+  <artifactId>java-h2g2</artifactId>
+
+  <name>JavaH2G2</name>
+
+  <developers>
+    <developer>
+      <email>arey@ippon.fr</email>
+      <name>Anthony REY</name>
+    </developer>
+    <developer>
+      <email>cdamon@ippon.fr</email>
+      <name>Colin DAMON</name>
+    </developer>
+  </developers>
+</project>
diff --git a/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java
new file mode 100644
index 0000000000000000000000000000000000000000..4dd9649bb6c67d6cb690d56f7e7f64523f61db8a
--- /dev/null
+++ b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java
@@ -0,0 +1,9 @@
+package fr.ippon.kata.h2g2;
+
+public enum Book {
+  FIRST,
+  SECOND,
+  THIRD,
+  FORTH,
+  FIFTH
+}
diff --git a/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java
new file mode 100644
index 0000000000000000000000000000000000000000..38cb3d290318dfa4c38352a141a6d5346a6e330f
--- /dev/null
+++ b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java
@@ -0,0 +1,117 @@
+package fr.ippon.kata.h2g2;
+
+import static java.util.stream.Collectors.*;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class Shop {
+  private static final Map<Integer, Float> FACTORS = buildFactors();
+  private static final int BOOK_PRICE = 8;
+
+  private static Map<Integer, Float> buildFactors() {
+    Map<Integer, Float> factors = new HashMap<>();
+
+    factors.put(5, 0.75f);
+    factors.put(4, 0.80f);
+    factors.put(3, 0.90f);
+    factors.put(2, 0.95f);
+    factors.put(1, 1f);
+    factors.put(0, 1f);
+
+    return factors;
+  }
+
+  public static float buy(Book... books) {
+    Basket basket = new Basket(books);
+
+    return (float) basket.getPacks().stream().map(packPrice()).reduce(BigDecimal.ZERO, BigDecimal::add).floatValue();
+  }
+
+  private static Function<Integer, BigDecimal> packPrice() {
+    return count -> BigDecimal.valueOf(BOOK_PRICE * count * FACTORS.get(count));
+  }
+
+  private static final class Basket {
+    private final Collection<Integer> packs;
+
+    public Basket(Book... books) {
+      packs = buildPacks(books);
+    }
+
+    private Collection<Integer> buildPacks(Book[] books) {
+      Map<Book, Integer> groupedBooks = groupBooks(books);
+
+      List<Integer> packs = firstPackLevel(groupedBooks);
+
+      return optimizePacks(packs);
+    }
+
+    private Collection<Integer> optimizePacks(List<Integer> packs) {
+      int threePack = countPackBySize(packs, 3);
+      int fivePack = countPackBySize(packs, 5);
+      int commonPacks = Math.min(fivePack, threePack);
+
+      List<Integer> result = new ArrayList<>();
+
+      result.addAll(repeatPack(1, countPackBySize(packs, 1)));
+      result.addAll(repeatPack(2, countPackBySize(packs, 2)));
+      result.addAll(repeatPack(3, threePack - commonPacks));
+      result.addAll(repeatPack(4, countPackBySize(packs, 4) + commonPacks * 2));
+      result.addAll(repeatPack(5, fivePack - commonPacks));
+
+      return result;
+    }
+
+    private List<Integer> repeatPack(int packSize, int packCount) {
+      return IntStream.range(0, packCount).mapToObj(key -> Integer.valueOf(packSize)).collect(Collectors.toList());
+    }
+
+    private int countPackBySize(List<Integer> packs, int size) {
+      return (int) packs.stream().filter(value -> value == size).count();
+    }
+
+    private List<Integer> firstPackLevel(Map<Book, Integer> groupedBooks) {
+      List<Integer> packs = new ArrayList<>();
+
+      while (remainingBooks(groupedBooks)) {
+        packs.add(groupedBooks.size());
+
+        groupedBooks =
+          groupedBooks.entrySet().stream().filter(booksRemaining()).collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue() - 1));
+      }
+      return packs;
+    }
+
+    private boolean remainingBooks(Map<Book, Integer> groupedBooks) {
+      return !groupedBooks.isEmpty();
+    }
+
+    private Predicate<Entry<Book, Integer>> booksRemaining() {
+      return entry -> entry.getValue() > 1;
+    }
+
+    private static Map<Book, Integer> groupBooks(Book... books) {
+      return Arrays
+        .stream(books)
+        .collect(groupingBy(Function.identity()))
+        .entrySet()
+        .stream()
+        .collect(toMap(Entry::getKey, entry -> entry.getValue().size()));
+    }
+
+    private Collection<Integer> getPacks() {
+      return packs;
+    }
+  }
+}
diff --git a/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java b/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..7595bc7ac4c11759dafc667eea4b1433d5786970
--- /dev/null
+++ b/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java
@@ -0,0 +1,54 @@
+package fr.ippon.kata.h2g2;
+
+import static fr.ippon.kata.h2g2.Book.*;
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class H2G2Test {
+
+  @Test
+  void shouldBeFreeWithoutBooks() {
+    assertThat(Shop.buy()).isEqualTo(0);
+  }
+
+  @Test
+  void shouldPayFullPriceForOneBook() {
+    assertThat(Shop.buy(FIRST)).isEqualTo(8);
+  }
+
+  @Test
+  void shouldPayFullPriceForTwoFirstBooks() {
+    assertThat(Shop.buy(FIRST, FIRST)).isEqualTo(8 * 2);
+  }
+
+  @Test
+  void shouldGetFivePercentOffWithTwoBooksFromSeries() {
+    assertThat(Shop.buy(FIRST, SECOND)).isEqualTo(8 * 2 * 0.95f);
+  }
+
+  @Test
+  void shouldGetTenPercentOffWithThreeBooksFromSeries() {
+    assertThat(Shop.buy(THIRD, FORTH, FIFTH)).isEqualTo(8 * 3 * 0.90f);
+  }
+
+  @Test
+  void shouldGetTwentyPercentOffWithFourBooksFromSeries() {
+    assertThat(Shop.buy(SECOND, THIRD, FORTH, FIFTH)).isEqualTo(8 * 4 * 0.80f);
+  }
+
+  @Test
+  void shouldGetTwentyFivePercentOffWithFiveBooksFromSeries() {
+    assertThat(Shop.buy(FIRST, SECOND, THIRD, FORTH, FIFTH)).isEqualTo(8 * 5 * 0.75f);
+  }
+
+  @Test
+  void shouldGetBestPossibleReductionForFirstFirstSecond() {
+    assertThat(Shop.buy(FIRST, FIRST, SECOND)).isEqualTo(8 + (8 * 2 * 0.95f));
+  }
+
+  @Test
+  void shouldGetBestPossibleReductionForBasketOfHell() {
+    assertThat(Shop.buy(FIRST, FIRST, SECOND, SECOND, THIRD, THIRD, FORTH, FIFTH)).isEqualTo(2 * 8 * 4 * 0.8f);
+  }
+}