diff --git a/src/main/java/cc/fascinated/common/packet/MinecraftJavaPacket.java b/src/main/java/cc/fascinated/common/packet/MinecraftJavaPacket.java
new file mode 100644
index 0000000..55d4dd9
--- /dev/null
+++ b/src/main/java/cc/fascinated/common/packet/MinecraftJavaPacket.java
@@ -0,0 +1,66 @@
+package cc.fascinated.common.packet;
+
+import lombok.NonNull;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Represents a packet in the
+ * Minecraft Java protocol.
+ *
+ * @author Braydon
+ * @see Protocol Docs
+ */
+public abstract class MinecraftJavaPacket {
+ /**
+ * Process this packet.
+ *
+ * @param inputStream the input stream to read from
+ * @param outputStream the output stream to write to
+ * @throws IOException if an I/O error occurs
+ */
+ public abstract void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException;
+
+ /**
+ * Write a variable integer to the output stream.
+ *
+ * @param outputStream the output stream to write to
+ * @param paramInt the integer to write
+ * @throws IOException if an I/O error occurs
+ */
+ protected final void writeVarInt(DataOutputStream outputStream, int paramInt) throws IOException {
+ while (true) {
+ if ((paramInt & 0xFFFFFF80) == 0) {
+ outputStream.writeByte(paramInt);
+ return;
+ }
+ outputStream.writeByte(paramInt & 0x7F | 0x80);
+ paramInt >>>= 7;
+ }
+ }
+
+ /**
+ * Read a variable integer from the input stream.
+ *
+ * @param inputStream the input stream to read from
+ * @return the integer that was read
+ * @throws IOException if an I/O error occurs
+ */
+ protected final int readVarInt(@NonNull DataInputStream inputStream) throws IOException {
+ int i = 0;
+ int j = 0;
+ while (true) {
+ int k = inputStream.readByte();
+ i |= (k & 0x7F) << j++ * 7;
+ if (j > 5) {
+ throw new RuntimeException("VarInt too big");
+ }
+ if ((k & 0x80) != 128) {
+ break;
+ }
+ }
+ return i;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketHandshakingInSetProtocol.java b/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketHandshakingInSetProtocol.java
new file mode 100644
index 0000000..d589ad2
--- /dev/null
+++ b/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketHandshakingInSetProtocol.java
@@ -0,0 +1,64 @@
+package cc.fascinated.common.packet.impl.java;
+
+import cc.fascinated.common.packet.MinecraftJavaPacket;
+import lombok.AllArgsConstructor;
+import lombok.NonNull;
+import lombok.ToString;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * This packet is sent by the client to the server to set
+ * the hostname, port, and protocol version of the client.
+ *
+ * @author Braydon
+ * @see Protocol Docs
+ */
+@AllArgsConstructor @ToString
+public final class JavaPacketHandshakingInSetProtocol extends MinecraftJavaPacket {
+ private static final byte ID = 0x00; // The ID of the packet
+ private static final int STATUS_HANDSHAKE = 1; // The status handshake ID
+
+ /**
+ * The hostname of the server.
+ */
+ @NonNull private final String hostname;
+
+ /**
+ * The port of the server.
+ */
+ private final int port;
+
+ /**
+ * The protocol version of the server.
+ */
+ private final int protocolVersion;
+
+ /**
+ * Process this packet.
+ *
+ * @param inputStream the input stream to read from
+ * @param outputStream the output stream to write to
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
+ try (ByteArrayOutputStream handshakeBytes = new ByteArrayOutputStream();
+ DataOutputStream handshake = new DataOutputStream(handshakeBytes)
+ ) {
+ handshake.writeByte(ID); // Write the ID of the packet
+ writeVarInt(handshake, protocolVersion); // Write the protocol version
+ writeVarInt(handshake, hostname.length()); // Write the length of the hostname
+ handshake.writeBytes(hostname); // Write the hostname
+ handshake.writeShort(port); // Write the port
+ writeVarInt(handshake, STATUS_HANDSHAKE); // Write the status handshake ID
+
+ // Write the handshake bytes to the output stream
+ writeVarInt(outputStream, handshakeBytes.size());
+ outputStream.write(handshakeBytes.toByteArray());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketStatusInStart.java b/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketStatusInStart.java
new file mode 100644
index 0000000..bf3a540
--- /dev/null
+++ b/src/main/java/cc/fascinated/common/packet/impl/java/JavaPacketStatusInStart.java
@@ -0,0 +1,62 @@
+package cc.fascinated.common.packet.impl.java;
+
+import cc.fascinated.common.packet.MinecraftJavaPacket;
+import lombok.Getter;
+import lombok.NonNull;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * This packet is sent by the client to the server to request the
+ * status of the server. The server will respond with a json object
+ * containing the server's status.
+ *
+ * @author Braydon
+ * @see Protocol Docs
+ */
+@Getter
+public final class JavaPacketStatusInStart extends MinecraftJavaPacket {
+ private static final byte ID = 0x00; // The ID of the packet
+
+ /**
+ * The response json from the server, null if none.
+ */
+ private String response;
+
+ /**
+ * Process this packet.
+ *
+ * @param inputStream the input stream to read from
+ * @param outputStream the output stream to write to
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
+ // Send the status request
+ outputStream.writeByte(0x01); // Size of packet
+ outputStream.writeByte(ID);
+
+ // Read the status response
+ readVarInt(inputStream); // Size of the response
+ int id = readVarInt(inputStream);
+ if (id == -1) { // The stream was prematurely ended
+ throw new IOException("Server prematurely ended stream.");
+ } else if (id != ID) { // Invalid packet ID
+ throw new IOException("Server returned invalid packet ID.");
+ }
+
+ int length = readVarInt(inputStream); // Length of the response
+ if (length == -1) { // The stream was prematurely ended
+ throw new IOException("Server prematurely ended stream.");
+ } else if (length == 0) {
+ throw new IOException("Server returned unexpected value.");
+ }
+
+ // Get the json response
+ byte[] data = new byte[length];
+ inputStream.readFully(data);
+ response = new String(data);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/controller/PlayerController.java b/src/main/java/cc/fascinated/controller/PlayerController.java
index 2dd5dbf..52bc8ec 100644
--- a/src/main/java/cc/fascinated/controller/PlayerController.java
+++ b/src/main/java/cc/fascinated/controller/PlayerController.java
@@ -1,11 +1,11 @@
package cc.fascinated.controller;
+import cc.fascinated.common.PlayerUtils;
import cc.fascinated.model.player.Player;
import cc.fascinated.model.player.Skin;
import cc.fascinated.model.response.impl.InvalidPartResponse;
import cc.fascinated.model.response.impl.PlayerNotFoundResponse;
import cc.fascinated.service.PlayerService;
-import cc.fascinated.common.PlayerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
diff --git a/src/main/java/cc/fascinated/controller/ServerController.java b/src/main/java/cc/fascinated/controller/ServerController.java
new file mode 100644
index 0000000..7adbd57
--- /dev/null
+++ b/src/main/java/cc/fascinated/controller/ServerController.java
@@ -0,0 +1,18 @@
+package cc.fascinated.controller;
+
+import cc.fascinated.model.server.MinecraftServer;
+import cc.fascinated.service.pinger.impl.JavaMinecraftServerPinger;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping(value = "/server/")
+public class ServerController {
+
+ @ResponseBody
+ @GetMapping(value = "/{hostname}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity getServer(@PathVariable String hostname) {
+ return ResponseEntity.ok(JavaMinecraftServerPinger.INSTANCE.ping(hostname, 25565));
+ }
+}
diff --git a/src/main/java/cc/fascinated/model/mojang/JavaServerStatusToken.java b/src/main/java/cc/fascinated/model/mojang/JavaServerStatusToken.java
new file mode 100644
index 0000000..ce98ab4
--- /dev/null
+++ b/src/main/java/cc/fascinated/model/mojang/JavaServerStatusToken.java
@@ -0,0 +1,13 @@
+package cc.fascinated.model.mojang;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+/**
+ * @author Braydon
+ */
+@AllArgsConstructor @Getter @ToString
+public final class JavaServerStatusToken {
+ private final String description;
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/model/mojang/MojangProfile.java b/src/main/java/cc/fascinated/model/mojang/MojangProfile.java
index 2c487c8..d10f97d 100644
--- a/src/main/java/cc/fascinated/model/mojang/MojangProfile.java
+++ b/src/main/java/cc/fascinated/model/mojang/MojangProfile.java
@@ -1,10 +1,10 @@
package cc.fascinated.model.mojang;
import cc.fascinated.Main;
-import cc.fascinated.model.player.Cape;
-import cc.fascinated.model.player.Skin;
import cc.fascinated.common.Tuple;
import cc.fascinated.common.UUIDUtils;
+import cc.fascinated.model.player.Cape;
+import cc.fascinated.model.player.Skin;
import com.google.gson.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/cc/fascinated/model/player/Player.java b/src/main/java/cc/fascinated/model/player/Player.java
index 4660ae5..021560e 100644
--- a/src/main/java/cc/fascinated/model/player/Player.java
+++ b/src/main/java/cc/fascinated/model/player/Player.java
@@ -1,8 +1,8 @@
package cc.fascinated.model.player;
-import cc.fascinated.model.mojang.MojangProfile;
import cc.fascinated.common.Tuple;
import cc.fascinated.common.UUIDUtils;
+import cc.fascinated.model.mojang.MojangProfile;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
diff --git a/src/main/java/cc/fascinated/model/player/Skin.java b/src/main/java/cc/fascinated/model/player/Skin.java
index bb27d6c..80206c4 100644
--- a/src/main/java/cc/fascinated/model/player/Skin.java
+++ b/src/main/java/cc/fascinated/model/player/Skin.java
@@ -1,7 +1,7 @@
package cc.fascinated.model.player;
-import cc.fascinated.config.Config;
import cc.fascinated.common.PlayerUtils;
+import cc.fascinated.config.Config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.JsonObject;
diff --git a/src/main/java/cc/fascinated/model/server/JavaMinecraftServer.java b/src/main/java/cc/fascinated/model/server/JavaMinecraftServer.java
new file mode 100644
index 0000000..21948d3
--- /dev/null
+++ b/src/main/java/cc/fascinated/model/server/JavaMinecraftServer.java
@@ -0,0 +1,10 @@
+package cc.fascinated.model.server;
+
+/**
+ * @author Braydon
+ */
+public final class JavaMinecraftServer extends MinecraftServer {
+ public JavaMinecraftServer(String hostname, int port, String motd) {
+ super(hostname, port, motd);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/model/server/MinecraftServer.java b/src/main/java/cc/fascinated/model/server/MinecraftServer.java
new file mode 100644
index 0000000..6028a00
--- /dev/null
+++ b/src/main/java/cc/fascinated/model/server/MinecraftServer.java
@@ -0,0 +1,15 @@
+package cc.fascinated.model.server;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+/**
+ * @author Braydon
+ */
+@AllArgsConstructor @Getter @ToString
+public class MinecraftServer {
+ private final String hostname;
+ private final int port;
+ private final String motd;
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/service/MojangAPIService.java b/src/main/java/cc/fascinated/service/MojangAPIService.java
index 56982c4..54c6330 100644
--- a/src/main/java/cc/fascinated/service/MojangAPIService.java
+++ b/src/main/java/cc/fascinated/service/MojangAPIService.java
@@ -1,8 +1,8 @@
package cc.fascinated.service;
+import cc.fascinated.common.WebRequest;
import cc.fascinated.model.mojang.MojangProfile;
import cc.fascinated.model.mojang.MojangUsernameToUuid;
-import cc.fascinated.common.WebRequest;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
diff --git a/src/main/java/cc/fascinated/service/PlayerService.java b/src/main/java/cc/fascinated/service/PlayerService.java
index 2ec5909..e24fb69 100644
--- a/src/main/java/cc/fascinated/service/PlayerService.java
+++ b/src/main/java/cc/fascinated/service/PlayerService.java
@@ -1,9 +1,9 @@
package cc.fascinated.service;
+import cc.fascinated.common.UUIDUtils;
import cc.fascinated.model.mojang.MojangProfile;
import cc.fascinated.model.mojang.MojangUsernameToUuid;
import cc.fascinated.model.player.Player;
-import cc.fascinated.common.UUIDUtils;
import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
diff --git a/src/main/java/cc/fascinated/service/pinger/MinecraftServerPinger.java b/src/main/java/cc/fascinated/service/pinger/MinecraftServerPinger.java
new file mode 100644
index 0000000..f223b1d
--- /dev/null
+++ b/src/main/java/cc/fascinated/service/pinger/MinecraftServerPinger.java
@@ -0,0 +1,9 @@
+package cc.fascinated.service.pinger;
+
+/**
+ * @author Braydon
+ * @param the type of server to ping
+ */
+public interface MinecraftServerPinger {
+ T ping(String hostname, int port);
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/cc/fascinated/service/pinger/impl/JavaMinecraftServerPinger.java
new file mode 100644
index 0000000..d4bd2f5
--- /dev/null
+++ b/src/main/java/cc/fascinated/service/pinger/impl/JavaMinecraftServerPinger.java
@@ -0,0 +1,53 @@
+package cc.fascinated.service.pinger.impl;
+
+import cc.fascinated.Main;
+import cc.fascinated.common.packet.impl.java.JavaPacketHandshakingInSetProtocol;
+import cc.fascinated.common.packet.impl.java.JavaPacketStatusInStart;
+import cc.fascinated.model.mojang.JavaServerStatusToken;
+import cc.fascinated.model.server.JavaMinecraftServer;
+import cc.fascinated.service.pinger.MinecraftServerPinger;
+import lombok.extern.log4j.Log4j2;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * @author Braydon
+ */
+@Log4j2(topic = "Java Pinger")
+public final class JavaMinecraftServerPinger implements MinecraftServerPinger {
+ public static final JavaMinecraftServerPinger INSTANCE = new JavaMinecraftServerPinger();
+
+ private static final int TIMEOUT = 3000; // The timeout for the socket
+
+ @Override
+ public JavaMinecraftServer ping(String hostname, int port) {
+ log.info("Pinging {}:{}...", hostname, port);
+
+ // Open a socket connection to the server
+ try (Socket socket = new Socket()) {
+ socket.setTcpNoDelay(true);
+ socket.connect(new InetSocketAddress(hostname, port), TIMEOUT);
+
+ // Open data streams to begin packet transaction
+ try (DataInputStream inputStream = new DataInputStream(socket.getInputStream());
+ DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
+ // Begin handshaking with the server
+ new JavaPacketHandshakingInSetProtocol(hostname, port, 47).process(inputStream, outputStream);
+
+ // Send the status request to the server, and await back the response
+ JavaPacketStatusInStart packetStatusInStart = new JavaPacketStatusInStart();
+ packetStatusInStart.process(inputStream, outputStream);
+ System.out.println("packetStatusInStart.getResponse() = " + packetStatusInStart.getResponse());
+ JavaServerStatusToken token = Main.GSON.fromJson(packetStatusInStart.getResponse(), JavaServerStatusToken.class);
+ return new JavaMinecraftServer(hostname, port, token.getDescription());
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return null;
+ }
+}
\ No newline at end of file