Java AES Encryption and Decryption: AES-256-GCM Example

Java AES encryption and decryption with AES-256-GCM, random IVs, key storage, PBKDF2 and HKDF keys, file encryption, tamper detection and common mistakes.

AES-256-GCM encryption and decryption flow in Java, a 256-bit key and a random 12-byte IV encrypt the plaintext into ciphertext plus a 16-byte tag, stored as IV, ciphertext and tag, and decryption either returns the plaintext or throws AEADBadTagException

AES (Advanced Encryption Standard) is a symmetric cipher, so one secret key both encrypts and decrypts the data. For AES encryption and decryption in Java, we call Cipher.getInstance(“AES/GCM/NoPadding”) with a 256-bit key and a new random 12-byte IV for every message. The GCM mode also adds a 16-byte authentication tag, so decryption fails with an exception if anyone changed the encrypted data.

We use AES for data that the app must read back later, such as API tokens, personal data in a database column, backup files or messages between two services that share a key. Everything we need is in the JDK packages javax.crypto and java.security, with no extra library.

The following example creates an AES-256 key, encrypts a short text and decrypts it again. The helper methods encrypt() and decrypt() come from section 3 and section 4.

KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(256);
SecretKey key = generator.generateKey();

byte[] encrypted = encrypt("card ends 4242".getBytes(StandardCharsets.UTF_8), key);
String stored = Base64.getEncoder().encodeToString(encrypted);       // random text, new on every run
int length = encrypted.length;                                       // 42 = 12 IV + 14 data + 16 tag

byte[] decrypted = decrypt(Base64.getDecoder().decode(stored), key);
String plain = new String(decrypted, StandardCharsets.UTF_8);        // "card ends 4242"

Notice that the encrypted value carries its own IV in the first 12 bytes, so we store one Base64 string and need only the key to decrypt it. We cover how AES and its modes work, the key, the encryption and decryption methods, IV rules, keys derived from a password, file encryption and the mistakes that make AES code insecure.

1. What Is AES (Advanced Encryption Standard)?

AES is the block cipher that NIST standardized in FIPS 197 in 2001 to replace DES. It encrypts data in blocks of 16 bytes (128 bits) and accepts keys of 128, 192 or 256 bits, which is where the names AES-128 and AES-256 come from. Because it is symmetric, the sender and the receiver must both hold the same secret key, so protecting and sharing that key is the hard part of any AES design.

A block cipher on its own encrypts only one 16-byte block. The mode of operation decides how longer data is split, chained and protected, and the mode matters as much as the key size.

ModeTransformation in JavaIVDetects tamperingUse it?
ECBAES/ECB/PKCS5PaddingNoneNoNever, equal blocks give equal output
CBCAES/CBC/PKCS5Padding16 bytes, randomNo, needs a separate HMACOnly for legacy formats
CTRAES/CTR/NoPadding16 bytes, uniqueNo, needs a separate HMACRarely, in special protocols
GCMAES/GCM/NoPadding12 bytes, uniqueYes, 16-byte tagYes, the default choice

GCM encrypts like a stream cipher, so it needs no padding, and it computes an authentication tag over the ciphertext. NIST SP 800-38D defines it, and TLS 1.3 uses it for most HTTPS traffic.

AES-256-GCM encryption and decryption flow in Java, a 256-bit key and a random 12-byte IV encrypt the plaintext into ciphertext plus a 16-byte tag, stored as IV, ciphertext and tag, and decryption either returns the plaintext or throws AEADBadTagException
The IV travels in front of the ciphertext, and decryption checks the tag before it returns any plaintext

1.1. Why Cipher.getInstance(“AES”) Is Dangerous

When we pass only “AES”, the SunJCE provider picks AES/ECB/PKCS5Padding. ECB encrypts every block on its own with no IV, so identical 16-byte blocks of plaintext produce identical blocks of ciphertext, and patterns in the data stay visible.

SecretKey key = newAesKey();
Cipher ecb = Cipher.getInstance("AES");
ecb.init(Cipher.ENCRYPT_MODE, key);
byte[] twoEqualBlocks = "same 16 bytes!!!same 16 bytes!!!".getBytes(StandardCharsets.UTF_8);
byte[] ecbOut = ecb.doFinal(twoEqualBlocks);

boolean leaks = Arrays.equals(Arrays.copyOfRange(ecbOut, 0, 16), Arrays.copyOfRange(ecbOut, 16, 32));   // true
byte[] ecbIv = ecb.getIV();                                                                             // null, ECB has no IV

Always pass the full transformation, AES/GCM/NoPadding, to Cipher.getInstance(). Static analysis tools such as SpotBugs and Sonar flag the short form for the same reason.

2. Creating an AES-256 Key

An AES key is 32 random bytes for AES-256. The class KeyGenerator creates one from a strong random source, and the class SecretKeySpec turns 32 bytes that we load from a secret store back into a SecretKey.

KeyGenerator aesGenerator = KeyGenerator.getInstance("AES");
aesGenerator.init(256);
SecretKey newKey = aesGenerator.generateKey();
int keyBytes = newKey.getEncoded().length;                              // 32

String exported = Base64.getEncoder().encodeToString(newKey.getEncoded());
SecretKey loaded = new SecretKeySpec(Base64.getDecoder().decode(exported), "AES");
boolean sameKey = Arrays.equals(loaded.getEncoded(), newKey.getEncoded());   // true

The examples in the following sections get their key from a small helper that wraps the same three KeyGenerator calls.

static SecretKey newAesKey() throws NoSuchAlgorithmException {
    KeyGenerator generator = KeyGenerator.getInstance("AES");
    generator.init(256);
    return generator.generateKey();
}

In production, the key does not live in the source code or in the same database as the data. We load it from a key management service such as AWS KMS, Azure Key Vault or HashiCorp Vault, from a Java KeyStore file, or from an environment secret injected at deploy time.

Old answers mention the JCE Unlimited Strength policy files for 256-bit keys. Since Java 9 (and 8u161), the unlimited policy is the default, so AES-256 works on any current JDK without extra files.

3. AES-256-GCM Encryption Example

Encryption takes four steps. We create a random 12-byte IV with SecureRandom, initialize the Cipher with the key and a GCMParameterSpec that holds the tag length of 128 bits and the IV, call doFinal(), and put the IV in front of the result. The output of doFinal() already contains the 16-byte tag at its end, and the three constants after the method are shared by all helpers in this article.

static byte[] encrypt(byte[] plaintext, SecretKey key) throws GeneralSecurityException {
    byte[] iv = new byte[IV_LENGTH];
    RANDOM.nextBytes(iv);

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
    byte[] ciphertext = cipher.doFinal(plaintext);

    return ByteBuffer.allocate(iv.length + ciphertext.length).put(iv).put(ciphertext).array();
}

static final int IV_LENGTH = 12;
static final int TAG_BITS = 128;
static final SecureRandom RANDOM = new SecureRandom();

The method works on bytes, which is what the cipher needs. For text, we convert with an explicit charset and encode the result with Base64 to get a String that fits into a database column, a JSON field or a properties file.

static String encryptText(String text, SecretKey key) throws GeneralSecurityException {
    byte[] encrypted = encrypt(text.getBytes(StandardCharsets.UTF_8), key);
    return Base64.getEncoder().encodeToString(encrypted);
}
SecretKey key = newAesKey();
String token1 = encryptText("refresh-token-abc", key);
String token2 = encryptText("refresh-token-abc", key);
boolean differ = !token1.equals(token2);                     // true, new IV each time
int base64Length = token1.length();                          // 60 for 45 bytes

We can see that encrypting the same text twice gives two different values, which is what we want. An observer cannot tell that two rows hold the same token.

4. AES-256-GCM Decryption Example

Decryption reverses the layout. We read the first 12 bytes as the IV, initialize the cipher in DECRYPT_MODE with the same key and tag length, and pass the rest to doFinal(). The method checks the tag first and returns the plaintext only if the tag matches.

static byte[] decrypt(byte[] ivAndCiphertext, SecretKey key) throws GeneralSecurityException {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, ivAndCiphertext, 0, IV_LENGTH));
    return cipher.doFinal(ivAndCiphertext, IV_LENGTH, ivAndCiphertext.length - IV_LENGTH);
}
static String decryptText(String base64, SecretKey key) throws GeneralSecurityException {
    byte[] plain = decrypt(Base64.getDecoder().decode(base64), key);
    return new String(plain, StandardCharsets.UTF_8);
}
SecretKey key = newAesKey();
String token = encryptText("refresh-token-abc", key);
String restored = decryptText(token, key);                   // "refresh-token-abc"

If a single bit of the stored value changes, or if we use the wrong key, doFinal() throws an AEADBadTagException, which is a subclass of GeneralSecurityException. The app never sees corrupted plaintext.

SecretKey key = newAesKey();
byte[] tampered = Base64.getDecoder().decode(encryptText("refresh-token-abc", key));
tampered[20] ^= 1;
byte[] fromTampered = decrypt(tampered, key);                // AEADBadTagException: Tag mismatch

SecretKey otherKey = KeyGenerator.getInstance("AES").generateKey();
String wrongKey = decryptText(encryptText("refresh-token-abc", key), otherKey);   // AEADBadTagException: Tag mismatch

We catch the exception at the boundary where the data enters the app, log the record ID without the data, and treat the value as invalid. Returning null from a catch-all block, as many examples do, hides tampering and key mix-ups.

5. IV Rules for AES-GCM

The IV (initialization vector) does not need to be secret, but it must never repeat for the same key. If two messages share a key and an IV in GCM mode, an attacker can recover the XOR of the two plaintexts and can forge new messages with valid tags. A random 12-byte IV per message from SecureRandom avoids that.

  • Use 12 bytes (96 bits), the length GCM is designed for.
  • Create a new random IV for every encrypt() call and store it with the ciphertext.
  • Rotate the key before it has encrypted about 232 messages with random IVs, the limit from NIST SP 800-38D.
  • Keep the 128-bit tag; shorter tags are allowed but weaker.

The JDK guards against the most common slip. A Cipher object refuses to be initialized for encryption again with the key and IV it used last time.

SecretKey key = newAesKey();
byte[] fixedIv = new byte[12];
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
gcm.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, fixedIv));
byte[] first = gcm.doFinal("a".getBytes(StandardCharsets.UTF_8));
gcm.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, fixedIv));   // InvalidAlgorithmParameterException: Cannot reuse iv for GCM encryption

That check covers only one Cipher instance. A hard-coded IV that a new instance uses on every call passes without any error, which is why the IV must come from SecureRandom.

6. Binding Context With Associated Data

GCM can also authenticate data that it does not encrypt, called associated data (AAD). We pass it with updateAAD() before doFinal(), and decryption succeeds only with the same AAD. A typical use is the ID of the database row, so an attacker who can write to the database cannot copy the encrypted token of one user into the row of another.

SecretKey key = newAesKey();
byte[] iv = new byte[12];
RANDOM.nextBytes(iv);
Cipher enc = Cipher.getInstance("AES/GCM/NoPadding");
enc.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
enc.updateAAD("user:42".getBytes(StandardCharsets.UTF_8));
byte[] boundToken = enc.doFinal("token".getBytes(StandardCharsets.UTF_8));

Cipher dec = Cipher.getInstance("AES/GCM/NoPadding");
dec.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
dec.updateAAD("user:43".getBytes(StandardCharsets.UTF_8));
byte[] movedRow = dec.doFinal(boundToken);                    // AEADBadTagException: Tag mismatch

7. Deriving an AES Key From a Password With PBKDF2

Sometimes the only secret is a password that a user types, for example to protect an exported backup file. A password is too short and too predictable to be an AES key, so we stretch it with PBKDF2, which runs HMAC-SHA256 many times over the password and a random salt. The OWASP Password Storage Cheat Sheet recommends 600,000 iterations for PBKDF2WithHmacSHA256, so guessing passwords against a stolen file becomes slow.

static SecretKey keyFromPassword(char[] password, byte[] salt) throws GeneralSecurityException {
    PBEKeySpec spec = new PBEKeySpec(password, salt, 600_000, 256);
    try {
        byte[] keyBytes = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();
        return new SecretKeySpec(keyBytes, "AES");
    } finally {
        spec.clearPassword();
    }
}

The salt is 16 random bytes, new for every file, and it is stored in front of the IV so that decryption can derive the same key. The layout of the stored value becomes salt, IV, ciphertext and tag.

byte[] salt = new byte[16];
RANDOM.nextBytes(salt);
SecretKey derived = keyFromPassword("correct horse".toCharArray(), salt);
byte[] backup = encrypt("my notes".getBytes(StandardCharsets.UTF_8), derived);
byte[] file = ByteBuffer.allocate(salt.length + backup.length).put(salt).put(backup).array();

byte[] readSalt = Arrays.copyOfRange(file, 0, 16);
SecretKey again = keyFromPassword("correct horse".toCharArray(), readSalt);
String notes = new String(decrypt(Arrays.copyOfRange(file, 16, file.length), again), StandardCharsets.UTF_8);   // "my notes"

When we already have a strong secret, such as a master key from a vault, and need several AES keys from it, PBKDF2 is the wrong tool because it is slow on purpose. Java 25 adds the KDF API (JEP 510) with HKDF, which derives separate keys for separate purposes.

byte[] masterKey = new byte[32];
RANDOM.nextBytes(masterKey);
KDF hkdf = KDF.getInstance("HKDF-SHA256");
AlgorithmParameterSpec params = HKDFParameterSpec.ofExtract()
        .addIKM(masterKey)
        .thenExpand("orders-db".getBytes(StandardCharsets.UTF_8), 32);
SecretKey ordersKey = hkdf.deriveKey("AES", params);
int ordersKeyLength = ordersKey.getEncoded().length;          // 32

8. Encrypting and Decrypting a File

For files, we write the IV first and stream the rest through a CipherOutputStream, so a large file never sits in memory during encryption. Decryption reads the IV back and wraps the input in a CipherInputStream. The post on copying an InputStream to an OutputStream explains the transferTo() call.

static void encryptFile(Path source, Path target, SecretKey key) throws IOException, GeneralSecurityException {
    byte[] iv = new byte[IV_LENGTH];
    RANDOM.nextBytes(iv);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));

    try (InputStream in = Files.newInputStream(source);
         OutputStream file = Files.newOutputStream(target)) {
        file.write(iv);
        try (OutputStream out = new CipherOutputStream(file, cipher)) {
            in.transferTo(out);
        }
    }
}
static byte[] decryptFile(Path source, SecretKey key) throws IOException, GeneralSecurityException {
    try (InputStream file = Files.newInputStream(source)) {
        byte[] iv = file.readNBytes(IV_LENGTH);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
        try (InputStream in = new CipherInputStream(file, cipher)) {
            return in.readAllBytes();
        }
    }
}
SecretKey key = newAesKey();
Path report = Files.writeString(Files.createTempFile("report", ".csv"), "id,total\n1,99.50\n");
Path locked = Files.createTempFile("report", ".enc");
encryptFile(report, locked, key);
long encryptedSize = Files.size(locked);                     // 45 = 12 + 17 + 16
String unlocked = new String(decryptFile(locked, key), StandardCharsets.UTF_8);   // "id,total\n1,99.50\n"

Closing the CipherOutputStream calls doFinal() and writes the tag, so the inner try-with-resources block matters. On decryption, a changed file makes CipherInputStream throw an IOException that wraps the AEADBadTagException. The SunJCE provider also holds the whole plaintext in memory until it has checked the tag, so for files of several hundred megabytes we split the data into chunks of a few megabytes, each with its own IV and tag.

9. Common AES Mistakes in Java Code

Most insecure AES code compiles and runs, and the round trip works, so tests do not catch it. These are the problems we see most often in code reviews, with the fix for each.

MistakeWhy it is wrongFix
Cipher.getInstance(“AES”)Falls back to ECB, which leaks patternsAES/GCM/NoPadding
Fixed or zero IVRepeating an IV under one key breaks GCMNew random 12-byte IV per message
CBC without an HMACChanged ciphertext goes unnoticed, padding oracle attacksGCM, or CBC plus an HMAC-SHA256 over IV and ciphertext
Key in source code or in the same databaseAnyone with the code or a dump can decryptKMS, vault or KeyStore
getBytes() without a charsetDifferent bytes on different platformsStandardCharsets.UTF_8
new String(ciphertext)Random bytes are not valid text and get damagedBase64 or raw byte[]
Encrypting user passwordsAnyone with the key gets every password backHash them with Argon2, bcrypt or PBKDF2
catch (Exception e) { return null; }Hides tampering and wrong keysLet AEADBadTagException reach the caller

The password row is worth a separate word. A login check never needs the original password, only proof that the user typed the same one, so we store a slow one-way hash instead of encrypting. The post on password hashing in Java covers that, and Spring Security password encoders do it for Spring apps.

10. Java AES Encryption and Decryption FAQs

The choice of key size and the errors during decryption cause most AES questions in Java.

10.1. Is AES-256 Better Than AES-128?

Both are secure today. AES-256 has a larger security margin, including against future quantum attacks, and runs 14 rounds instead of 10, so it is somewhat slower, but on CPUs with AES instructions the difference rarely matters. Many compliance rules ask for 256 bits, so AES-256 is the common default.

10.2. What Is the Default AES Mode in Java?

With Cipher.getInstance(“AES”), the SunJCE provider uses AES/ECB/PKCS5Padding. ECB is insecure for almost all data, so we always pass the full transformation.

10.3. Why Does Decryption Throw AEADBadTagException?

The tag check failed. The data was changed, the key or the IV differs from the one used for encryption, the associated data does not match, or the code cut off the IV or the tag when it stored the value.

10.4. Should I Use AES to Store Passwords?

No. Encryption is reversible by anyone who holds the key. Passwords are stored as salted, slow hashes such as Argon2id, bcrypt or PBKDF2, which can be checked but not reversed.

10.5. How Long Is AES-GCM Output?

The output is the plaintext length plus 16 bytes of tag, plus the 12-byte IV that we store in front. A 14-byte text gives 42 bytes, which Base64 turns into 56 characters.

10.6. Can I Use AES/CBC/PKCS5Padding Instead of GCM?

Only to read existing data that was encrypted that way. CBC needs a random 16-byte IV and a separate HMAC over the IV and ciphertext to detect tampering, and getting that combination right takes more code than GCM, which does both in one call.

11. Conclusion

AES encryption and decryption in Java comes down to one transformation, AES/GCM/NoPadding, a 256-bit key from KeyGenerator or a key store, and a fresh 12-byte IV from SecureRandom for every message. We store the IV in front of the ciphertext and the tag, and decryption either returns the original bytes or throws an AEADBadTagException.

When the secret is a password, PBKDF2 with 600,000 iterations and a random salt turns it into a key, and HKDF from the Java 25 KDF API splits a master key into several keys. Most real-world AES bugs come from ECB, repeated IVs and keys stored next to the data, not from the algorithm itself.

12. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. if (decryptedString != null) {

    should be replaced by

    if (!decryptedString.equals(encryptedString)) {

  2. Great job Lokesh.. Wonderful article. I have modified the code to use a file with live data. But the encryption algorithm takes time. For 110 lines it took about a minute to encrypt.

    For large data files try using multi-threading with 4 threads (or as many as your system supports) and see if it improves the performance.

    • Hi Jagan, I am not sure what you are proposing. We use multithreading when we can break a large task into multiple small tasks, and each task runs in a different thread. Here, you have a file and I am assuming that you want to encrypt the whole file as a single input. How multi-threading will help here?

      If you plan to somehow break the file content into parts (such as lines), and then encrypt each line independently, store it and later use it; then you can use Callable as discussed in these files in git.

  3. Hi Team,

    Thanks for the awesome tutorial.

    Do you have an alternative of the same encryptions methods in swift?

    Thanks in advance,
    Ram

  4. Thanks for the contribution, but you should warn readers about the use of hardcoded salt and IV. I know some people that used your exemple “as is” in a project, hardcoding a fixed salt and a fixed IV for all their encryptions and decryptions!

  5. Error while encrypting: java.security.NoSuchAlgorithmException: PBKDF2WithHmacSHA256 SecretKeyFactory not available

  6. AES 256 encrypt and decrypt method is take too much of time during retrieve multiple rows query , how quickly decrypt multiple rows retrieve using java

  7. You didn’t mention the import statements in the code sample. I am getting conflicts for that when i am trying to use this.Can u provide the import statements too so that we can use that code with out conflicts as it is having some conflicts in importing the some other packages.

    • import java.security.spec.KeySpec;
      import java.util.Base64;

      import javax.crypto.Cipher;
      import javax.crypto.SecretKey;
      import javax.crypto.SecretKeyFactory;
      import javax.crypto.spec.IvParameterSpec;
      import javax.crypto.spec.PBEKeySpec;
      import javax.crypto.spec.SecretKeySpec;

  8. Did you just list “secure passwords” as an example for encryption and decryption? Did you consider using hashing instead of encryption for that?

  9. How are people using this code example? You attempt to use secretKey to init spec, before secretKey is even defined. I tried rearranging the order of declarations, but they all depend on something else in a way such that it is impossible to use as written. If I declare secretKey before the KeySpec declaration – that won’t work because secretKey declaration requires tmp which needs spec and spec can’t be defined until secret key exists.

    Quoted code snippet (from encrypt):

    SecretKeyFactory factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA256”);
    KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), “AES”);

    • Nevermind I got it. The variable names overlap, but the toCharArray() is in reference to the string value.

  10. Hi Lokesh,

    I want to use your encryption and decryption methods. But I m getting this error ;
    “java.security.NoSuchAlgorithmException: PBKDF2WithHmacSHA256 SecretKeyFactory not available”.
    I cant solve this problem.
    I use Java 1.6 version.
    How can I fix it.

    Thank you,

  11. hi Lokesh,

    I have used your example to do AES encrypt/decrypt. Here is problem, if i run it standalone it works, however when i copy the encrypted string to a properties file and read it in groovy/grails webapplication ( which uses your code to decrypt), i am getting error like :
    I am getting java.lang.IllegalArgumentException: Illegal base64 character 5c.

    so i changed the decode code to the following :
    return new String(cipher.doFinal(Base64.getMimeDecoder().decode(strToDecrypt)));

    Now its giving me a different error :
    Error while decrypting: java.lang.IllegalArgumentException: Last unit does not have enough valid bits

    btw the input string i have which is encrypted by your example is of length 15 characters.
    and the encrypted string is of length 24 characters
    this is the encrypted string :
    vHsfqebYndXnWc78jk/qsQ==

    I have been trying to make this work for the last two days with little success, as always in a time crunch, any help is truly appreciated.

    Thanks
    Chandra

  12. AES uses the same secret key is used for the both encryption and decryption. Unlike AES 128 bit encryption and decryption, if we need a stronger AES 256 bit key, we need to have Java cryptography extension (JCE) unlimited strength jurisdiction policy files.

    If we have not installed the JCE we will be getting the error like “java.security.InvalidKeyException: Illegal key size” or “org.apache.xml.security.encryption.XMLEncryptionException: Illegal key size or default parameters”

  13. If i pass key as 256 bit(string length as 32) the algorithm gives the error as invalid key , Is this implementation for 256bit key or just 128 bit key( string length 16 )?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.