5 Core Concepts of Spring Security

  1. Authentication and Authorization
    – Authentication – Who are you – Answer by showing ID(Facebook, LinkedIn for ID which uniquely identifies you)
    – Authorization – What you want – State what you want

    Knowledge Based Authentication – Providing details you know about you to prove its you. Downside is details can be stolen.
    Possession Based Authentication – Key Cards for accessing Building Doors, Phone OTP. Authenticates by checking the user posses something which
    realuser should posses.

    Multi Factor Authentication – Enter password and enter OTP(KBA + PBA)

  2. Authorization – Checks whether the person is allowed to do something. For Authorization, Authentication is needed at first place.
  3. Principal
    – Person identified through process of Authentication
    – Person who has logged in. Currently logged in user (or) account.
    – App remembers the principal in context as currently loggedin user.
  4. Granted Authority
    – Authority includes whether the user is allowed to Read, Write, Update and Delete at permission level
  5. Role
    – Group of Authorities assigned together forms a role

Formbased Authentication
pom.xml

.
.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
.
.

Basic Auth
null

  1. Client Sends a request without username and password and gets 401 Unauthorized as Response
  2. Now Client Sends a request with username and password with Base64 Encoding
  3. Server validates whether user exists in DB
  4. Server replies with 200 Ok if user authentication is successful
  5. Basic ENCODED-BASE64-USERIDPASSWORD is the one sent in header to server from client
  6. In postman basic auth can be done by adding Authorization and base64 encoded user and password to header
    Header : Authorization
    Value : Basic base64('YourOrgName:YourAPIKEY');
    
  7. Base64 encoded text can be got from JS Console in browser as below

    "username:password!" // Here I used basic Auth string format
    
    // Encode the plain string to base64
    btoa("username:password!"); // output: "dXNlcm5hbWU6cGFzc3dvcmQh"
    
    
    // Decode the base64 to plain string
    atob("dXNlcm5hbWU6cGFzc3dvcmQh"); // output: "username:password!"
    
  8. Using Authorization Tab in post man does the same thing of adding base64 encoded UserName and Password to Header prepending Basic

The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character.

Form-based authentication
Form-based authentication is not formalized by any RFC.They don’t use the formal HTTP authentication techniques.They use the standard HTML form fields to pass the username and password values to the server.The server validates the credentials and then creates a “session” that is tied to a unique key that is passed between the client and server on each http put and get request.When the user clicks “log off” or the server logs the user off (for example after certain idle time), the server will invalidate the session key, which makes any subsequent communication between the client and server require re-validation

null

Basic Auth
null

Basic Auth with Authorization in Headers as seen in DevTool
null

Creating the below class in Spring Boot project would enable the Basic auth(httpAuth) instead of default formbased auth which we get after adding spring security starter dependency to pom.xml

ApplicationSecurityConfig.java
Using Custom Username and Password for Inmemory Authentication

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

Whitelisting some URLs(index, js and CSS files)

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

Authentication with password with no encryption

@Override
@Bean
 protected UserDetailsService userDetailsService() {
       UserDetails mugilUsrBuilder = User.builder()
              .username("Mugil")
              .password("{noop}password")
              .roles("ADMIN")
              .build();

      return new InMemoryUserDetailsManager(mugilUsrBuilder);
}

If {noop} is not used in password Spring security would throw an error asking to encode the password with password encoder as below.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

Using Password Simple Encoder
PasswordConfig.java

@Configuration
public class PasswordConfig {
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder(10);
    }
}

ApplicationSecurityConfig.java

  1. Inject the passwordEncoder from PasswordConfig class to ApplicationSecurityConfig
  2. Encode the password using instance of injected encoder in ApplicationSecurityConfig
 @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails mugilUsrBuilder = User.builder()
                .username("Mugil")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(mugilUsrBuilder);
    }

Allowing Access to API based on Role – Authorization
ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
.
.
 @Override
    @Bean
    //Authentication
    protected UserDetailsService userDetailsService() {
        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);  
    }

    @Override
    //Authorization
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                     //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers("/api/**").hasRole("ADMIN")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
.
.
  1. In the above code we have added two roles – ADMIN and USER
  2. Both were authenticated to access the application.But to access the API the role should be ADMIN
      antMatchers("/api/**").hasRole("ADMIN")
    
  3. If the user with Role USER try to access API then it would end up in 403 – Forbidden Error

Access Allowed

Forbidden Access

Allowing Access based on 2 Different Role
ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
.
.

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.csrf().disable()
                     .authorizeRequests()
                     //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                    .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                    .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                    .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}
  1. In the above piece of code we have defined two roles ADMIN and USER
  2. Those with USER role can access the API with HTTP Get Method. That means both ADMIN and USER role could access all the API using GET method
    .
    .antMatchers(HttpMethod.GET,"/api/**").permitAll()
    .
    
  3. Those with ADMIN role can access the API with HTTP POST, DELETE and PUT Method which corresponds to Create, Delete and Update as per Open API Specifiaction.

    .
    .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
    .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
    .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
    .
    
  4. The above could be cross checked by changing postman call with HttpMethods and Credentials

StudentsService.java

@RestController
@RequestMapping("/api/v1/students")
public class StudentsService {
    @Autowired
    StudentRepo studentRepo;

    @GetMapping(path="{studentId}")
    public Student getStudentById(@PathVariable("studentId") String studentId){
        return studentRepo.getStudentById(studentId);
    }

    @GetMapping
    public List<Student> getStudentList(){
        return studentRepo.getStudentsList();
    }

    @PutMapping
    public String updateStudent(@RequestBody Student student){
        return studentRepo.updateStudent(student);
    }

    @PostMapping
    public String addStudent(@RequestBody Student student){
        if(studentRepo.addStudents(student))
            return  "Student with Id " + student.getStudentId() + " added successfully";
        else
            return  "Error:Unable to create Student";
    }

    @DeleteMapping(path="{studentId}")
    public String deleteStudent(@PathVariable("studentId") String studentId){
        studentRepo.deleteStudent(studentId);
        return "Student Deleted Successfully";
    }
}

Allowing Access based on 2 Different Authority(or)Permission

  1. In the below code instead of using ROLES to authorize users to do something we use AUTHORITIES to allow user
  2. There are two ways to do this. One is by using hasAuthority in configure(HttpSecurity httpSecurity) method as below
  3. .
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    .
    .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
    .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
    .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
    .
    
  4. Other is by using @preauthorize annotation to decide the methods
    which could be allowed access to

    .
    .
    @PreAuthorize("hasAuthority('READ')")
    public List getStudentList(){
    .
    
    @PreAuthorize("hasAuthority('WRITE')")
    public String updateStudent(@RequestBody Student student){
    .
    .
    

ApplicationSecurityConfig.java
Using hasAuthority

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
        GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};

        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ", "WRITE")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.csrf().disable()
                     .authorizeRequests()
                     .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
                    .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
                    .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

ApplicationSecurityConfig.java
Using @PreAuthorize and EnableGlobalMethodSecurity

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
        GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};

        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ", "WRITE")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.csrf().disable()
                     .authorizeRequests()
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

StudentsService.java

@RestController
@RequestMapping("/api/v1/students")
public class StudentsService {
    @Autowired
    StudentRepo studentRepo;

    @GetMapping(path="{studentId}")
    public Student getStudentById(@PathVariable("studentId") String studentId){
        return studentRepo.getStudentById(studentId);
    }

    @GetMapping
    @PreAuthorize("hasAuthority('READ')")
    public List<Student> getStudentList(){
        return studentRepo.getStudentsList();
    }

    @PutMapping
    @PreAuthorize("hasAuthority('WRITE')")
    public String updateStudent(@RequestBody Student student){
        return studentRepo.updateStudent(student);
    }

    @PostMapping
    @PreAuthorize("hasAuthority('WRITE')")
    public String addStudent(@RequestBody Student student){
        if(studentRepo.addStudents(student))
            return  "Student with Id " + student.getStudentId() + " added successfully";
        else
            return  "Error:Unable to create Student";
    }

    @DeleteMapping(path="{studentId}")
    @PreAuthorize("hasAuthority('WRITE')")
    public String deleteStudent(@PathVariable("studentId") String studentId){
        studentRepo.deleteStudent(studentId);
        return "Student Deleted Successfully";
    }
}
  1. What is the difference between FormBased and BasicAuth?
    The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character. BasicAuth happens at http Protocol level where as Formbased Auth happens at Framework level.
  2. What is the difference between JKS and PKCS12?
    JKS is the most common if you stay within the Java world. PKCS#12 isn’t Java-specific, it’s particularly convenient to use certificates (with private keys) backed up from a browser or coming from OpenSSL-based tools.JKS, Java Key Store. You can find this file at sun.security.provider.JavaKeyStore. This keystore is Java specific, it usually has an extension of jks. This type of keystore can contain private keys and certificates, but it cannot be used to store secret keys. Since it’s a Java specific keystore, so it cannot be used in other programming languages.PKCS12, this is a standard keystore type which can be used in Java and other languages. You can find this keystore implementation at sun.security.pkcs12.PKCS12KeyStore. It usually has an extension of p12 or pfx. You can store private keys, secret keys and certificates on this type.

  3. How safe is Userid and Password in Basic Auth?
    UserId and Password in BasicAuth would transferred using base64 encoded UserName and Password attached to Header prepending Basic

    Authorization : Basic base64(UserId:Password)
    
  4. How POST, PUT and DELETE works in Basic Auth?
    In Basic Auth with respect to Spring Security CSRF Token would be used incase CSRF is enabled. If CSRF is disabled Authorization in header would be used
    for access. csrf().disable() should be always used incase you are using other than GET method.
  5. What is JSESSIONID?
    JSESSIONID which is generated and Stored as Cookie in Postman helps the spring boot app to recognizane whether the User is Authenticated or not.
  6. How Spring Security handles CSRF in client Side when enabled?
    Spring Security generates JSESSIONID and XSRF-TOKEN. Both were Set in Cookie. On Subsequent request X-XSRF-TOKEN should be sent in Header for Authorization.
  7. What happens when I delete XSRF token in Cookie?
    New Token would be generated based on JSESSIONID in cookie
  8. What happens when I delete XSRF token and in Cookie and try POST, DELETE and PUT over API?
    New JSESSIONID would be generated and placed in cookie. For this X-XSRF-TOKEN should be passed in header.
  9. Where is JSESSION stored?
    JSESSION is session ID which is stored in Inmemory Database in Server and in a Cookie in Client Side.
  10. What is the default Expiration time of JSESSION Cookie?
    30 Minutes
  11. Spring Security Arcitecture?
  12. Encoding vs Encrytion vs Hashing?
    Encoding refers to any transformation of a given input. For example, if we have a function x that reverses a string, function x -> y applied to ABCD produces DCBA.

    x -> y
    

    Encryption is a particular type of encoding where, to obtain the output, you provide both the input value and a key. The key makes it possible for choosing afterward who should be able to reverse the function (obtain the input from the output). The simplest form of representing encryption as a function looks like this:

    (x, k) -> y
    

    where x is the input, k is the key, and y is the result of the encryption. This way, an individual knows the key can use a known function to obtain the input from the output (y, k) -> x. We call this reverse function decryption. If the key used for encryption is the same as the one used for decryption, we usually call it a symmetric key.If we have two different keys for encryption ((x, k1) -> y) and decryption ((y,k2) -> x), then we say that the encryption is done with asymmetric keys. Then (k1,k2) is called a key pair. The key used for encryption, k1, is also referred to as the public key, while k2 is known as the private one. This way, only the owner of the private key can decrypt the data.

    Hashing is a particular type of encoding, except the function is only one way. That is,from an output y of the hashing function, you cannot get back the input x. However,there should always be a way to check if an output y corresponds to an input x, so we can understand the hashing as a pair of functions for encoding and matching. If hashing is x -> y, then we should also have a matching function (x,y) ->boolean.

    (x,y) ->boolean
    

    Sometimes the hashing function could also use a random value added to the input:
    (x, k) -> y. We refer to this value as salt. The salt makes the function stronger, enforcing the difficulty of applying a reverse function to obtain the input from the
    result.

  13. Two ways of Configuring Spring Security?
    Method 1: Adding userservice and passwordEncoder in configure method(Not Recommended)

    @Configuration
    public class ProjectConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("john")
                    .password("12345")
                    .authorities("read")
                    .and()
                    .passwordEncoder(NoOpPasswordEncoder.getInstance());
        }
    }
    

    Method 2: Using Seperate Configuration class for Bean and injecting beans(Recommended Method)
    PasswordConfig.java

    @Configuration
    public class PasswordConfig {
        @Bean
        public PasswordEncoder passwordEncoder()
        {
            return new BCryptPasswordEncoder(10);
        }
    }
    

    ApplicationSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
       @Autowired
       private PasswordEncoder passwordEncoder; 
     
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
    	   UserDetails mugilUsrBuilder = User.builder()
    			   .username("Mugil")
    			   .password(this.passwordEncoder.encode("password"))
    			   .roles("ADMIN")
    			   .build();
     
    	   return new InMemoryUserDetailsManager(mugilUsrBuilder);
        }
    
     
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.csrf().disable()
                         .authorizeRequests()
                         //Whitelisting URLS
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                        .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    
  14. What are the interface methods available in Spring Security?
    UserDetailsManager
    public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
    }
    UserDetailsService
    public interface UserDetailsService {
    UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException;
    }
    UserDetails
    public interface UserDetails extends Serializable {
    String getUsername();
    String getPassword();
    Collection<? extends GrantedAuthority>
    ➥getAuthorities();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
    }
    AuthenticationProvider
    public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
    boolean supports(Class<?> authentication);
    }
    Authentication
    public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated)
    throws IllegalArgumentException;
    }
  1. Advantage of JSON token is it could be used form multiple platforms like PC, Mobile for accessing secured pages
  2. User enters user credentials(Username and Password) and gets authenticated.
  3. User Credentails are validated and token is generated
  4. On Subsequent request from APIs tokens are used in header for authorization
  5. Tokens are generated and verified by filters.There would be line of filters on way from request to API
  6. JwtUsernameAndPasswordAuthenticationFilter generates the Token after authentication
  7. JwtTokenVerifierFilter authorizes the user in each and every request by interpreting the token in the request header

JwtTokenVerifierFilter.java

public class JwtTokenVerifierFilter extends OncePerRequestFilter {
    private final SecretKey secretKey;
    private final JwtConfig jwtConfig;

    public JwtTokenVerifierFilter(SecretKey secretKey,
                                  JwtConfig jwtConfig) {
        this.secretKey = secretKey;
        this.jwtConfig = jwtConfig;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {

        String authorizationHeader = httpServletRequest.getHeader(jwtConfig.getAuthorizationHeader());


        if(Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith("Bearer ")){
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");

        try{
            Jws<Claims> claimsJws = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(token);

            Claims body     = claimsJws.getBody();
            String username = body.getSubject();


            List<Map<String, String>> authorities = (List<Map<String, String>>)body.get("authorities");
            Set<SimpleGrantedAuthority> simpleGrantedAuth = authorities.stream()
                    .map(m -> new SimpleGrantedAuthority(m.get("authority")))
                    .collect(Collectors.toSet());

            Authentication authentication = new UsernamePasswordAuthenticationToken(username,  null, simpleGrantedAuth);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch (JwtException e){
            throw new IllegalStateException(String.format("Token %s cannot be Trusted", token));
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

JwtUsernameAndPasswordAuthenticationFilter.java

public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;
    private final JwtConfig jwtConfig;
    private final SecretKey secretKey;


    public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
                                                      JwtConfig jwtConfig,
                                                      SecretKey secretKey) {
        this.authenticationManager = authenticationManager;
        this.jwtConfig = jwtConfig;
        this.secretKey = secretKey;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        try {
            UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
                    .readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);

            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authenticationRequest.getUsername(),
                    authenticationRequest.getPassword()
            );

            Authentication authenticate = authenticationManager.authenticate(authentication);
            return authenticate;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(authResult.getName())
                .claim("authorities", authResult.getAuthorities())
                .setIssuedAt(new Date())
                .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(jwtConfig.getTokenExpirationAfterDays())))
                .signWith(secretKey)
                .compact();

        response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + token);
    }
}

Download Code

  1. Till now we were sending requests over HTTP. Now lets make HTTP secured using https by installing certificate in our project
  2. To generate certificate we use Key Store Explorer and will generate PKCS#12 Certificate

  3. Generate a public and private key and save the keystore
  4. The Keystore should be copied to the resources folder and the credentials of the keystore should be added to application.properties as below.

    application.properties

    # The format used for the keystore. It could be set to JKS in case it is a JKS file
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:TestCert
    # The password used to generate the certificate
    server.ssl.key-store-password=password
    
  5. Since the certificare generated(keystore file) cannot be cross checked with CA(Certificate Authority) it would display message like one below for the authenticy of certificate, whether it should be accpeted or not. How ever you can continue further by clicking on advanced

Note: While performing CRUD the addition of user is a JSON request which should be carried out only from postman. For this enable interceptor plugin in Chrome so that cookies set in chrome would be available in Postman.

Further Reads:
How SSL Works

Streams

  1. Intermediate Operations returns Stream as output. methods like map(), sorted(), distinct() would return stream as output
  2. Terminal Operations returns something other than Stream. Collect() returns collection. Reduce() returns one element

Converting list to a stream and print them

List<Integer> arrNumbers = List.of(10,27,31, 35, 40, 44, 48, 50);
//Convert list to stream
Stream objStream = arrNumbers.stream();

//Static Method Reference
objStream.forEach(System.out::println);

Both Map and Filter returns Streams.
Filter takes predicate as input and adds element to stream based on result of predicate(I.E. True or false).
Map takes function as input and adds transformed object value to stream.
Reduce takes function as input and returns a single value that is computed going over the entire rows in stream.

Filter
Filter is used for filtering the data, it always returns the boolean value. If it returns true, the item is added to list else it is filtered out (ignored)

  1. Filter takes a predicate as argument. Predicate returns boolean. The Output of Filter is a Stream
  2. Predicate object which is technically a function to convert an object to boolean. We pass an object and it will return true or false.
  3. filter() method is lazy like map, it wont be evaluated until you call a reduction method, like collect

Using Filter to filter names in list and print them

import java.util.*;
import java.util.stream.Stream;

List<String> arrNames = List.of("Mugil", "Mani", "Madhu");
arrNames.stream()
        .filter(name -> name.equals("Mugil"))
        .forEach(System.out::println);
List<Integer> arrNumbers = List.of(10,27,31, 35, 40, 44, 48, 50);
List<String> arrSkills = List.of("Spring Boot", "Spring Security", "Java 8", "Microservices", "Spring MVC");

//Printing Even Numbers
System.out.println("------Even Numbers-------");
arrNumbers.stream().filter(FilterEgs::isEven).forEach(System.out::println);

//Printing Even Numbers
System.out.println("------Even Numbers-------");
arrNumbers.stream().filter(no -> no%2==0).forEach(System.out::println);

//Printing Odd Numbers
System.out.println("------Odd Numbers-------");
arrNumbers.stream().filter(no -> no%2==1).forEach(System.out::println);

//Printing Odd Numbers
System.out.println("------Print Spring Skills-------");
arrSkills.stream().filter(skill -> skill.toLowerCase().contains("spring"))
.forEach(System.out::println);

private static boolean isEven(int no){
        return no%2==0;
}       

Output

------Even Numbers-------
10
40
44
48
50
------Odd Numbers-------
27
31
35
------Print Spring  Skills-------
Spring Boot
Spring Security
Spring MVC

Map
By using map, you transform the object values. the map returns the transformed object value as Stream.

  1. Map takes a function as a argument and performs transform action on elements. The Output of Map is a Stream
  2. map() is used to transform one object into another by applying a function.
  3. Stream.map(Function mapper) takes a function as an argument.
  4. mapping function converts one object to the other. Then, the map() function will do the transformation for you. It is also an intermediate Stream operation, which means you can call other Stream methods, like a filter, or collect on this to create a chain of transformations.
List<Integer> arrNum = List.of(5,6,8,57,4,1,26,84,15);
List<String> arrSkills = List.of("Spring Boot", "Spring Security", "Java 8", "Microservices", "Spring MVC");


//Printing Square of Numbers whose value less than 15
System.out.println("------Square of Numbers-------");
arrNum.stream()
      .filter(num -> num<15)   
      .map(num -> num*num)
      .forEach(System.out::println);


//Print Total Characters in String
System.out.println("------Print Total Characters in String-------");
arrSkills.stream()
		.map(skill -> skill + " contains "+ skill.length()+ " Characters")
		.forEach(System.out::println);

Output

------Square of Numbers-------
25
36
64
16
1
------Print Total Characters in String-------
Spring Boot Contains 11 Characters
Spring Security Contains 4 Characters
Java 8 Contains 5 Characters

Reduce

  1. Stream.reduce() takes function as Parameter
  2. Reduction stream operations allow us to produce one single result from a sequence of elements, by repeatedly applying a combining operation to the elements in the sequence.
  3. Stream.reduce() operation Contains Identity, Accumulator and Combiner
  4. Identity – an element that is the initial value of the reduction operation and the default result if the stream is empty
  5. Accumulator – a function that takes two parameters: a partial result of the reduction operation and the next element of the stream
  6. Combiner – a function used to combine the partial result of the reduction operation when the reduction is parallelized or when there’s a mismatch between the types of the accumulator arguments and the types of the accumulator implementation

ReduceEgs.java

import java.util.Arrays;
import java.util.List;

public class ReduceEgs {
    public static void main(String[] args) {
        List<Integer> arrNumbers =  Arrays.asList(1,2,3,4,5,6,7,8,9);

        System.out.println(arrNumbers.stream().reduce(0, ReduceEgs::sumNos));
    }

    public static Integer sumNos(int x, int y){
        System.out.println("a -"+ x + " b - "+  y);

        return x+y;
    }
}

Output

a -0 b - 1
a -1 b - 2
a -3 b - 3
a -6 b - 4
a -10 b - 5
a -15 b - 6
a -21 b - 7
a -28 b - 8
a -36 b - 9
45

In the above example

  1. the Integer value 0 is the identity. It stores the initial value of the reduction operation and also the default result when the stream of Integer values is empty.
  2. the lambda expression x+y represented by function sumNos is the accumulator since it takes the partial sum of Integer values and the next element in the stream.
  3. When a stream executes in parallel, the Java runtime splits the stream into multiple substreams. In such cases, we need to use a function to combine the results of the substreams into a single one. This is the role of the combiner

Role of combiner would be visibile only in parallelStream with output as below. It would be hard to interpret the output response in console.

ReduceEgs.java

.
.
 System.out.println(arrNumbers.parallelStream().reduce(0, ReduceEgs::sumNos));
.
.

Output

a -0 b - 6
a -0 b - 5
a -5 b - 6
a -0 b - 1
a -0 b - 7
a -0 b - 9
a -0 b - 2
a -1 b - 2
a -0 b - 3
a -0 b - 8
a -8 b - 9
a -0 b - 4
a -3 b - 4
a -3 b - 7
a -7 b - 17
a -11 b - 24
a -10 b - 35
45

Alternate ways to Sum Nos

public static void main(String[] args) {
 List<Integer> arrNumbers =  Arrays.asList(1,2,3,4,5,6,7,8,9);

 Integer sum = arrNumbers.stream().reduce(0, Integer::sum);
 Integer sum2 = arrNumbers.stream().reduce(0, (x,y)->x+y);

 System.out.println(sum);
 System.out.println(sum2);
}

Once you get the values in stream then it should be typecast to its original type before calling its methods as values are stored as Objects in stream

Below you could see the name is typecast to String before calling lowerCase method

List<String> arrNames = Arrays.asList("Mugil", "Mani", "Vinu");
Stream arrNameStream = arrNames.stream();
arrNameStream.forEach(name -> System.out.println(((String)name).toLowerCase()));
  1. What is the Difference between Map and Filter?
    Both perform intermediate Operations and returns stream as output.By using map, you transform the object values.
    Map returns a stream consisting of the results of applying the given function to the elements of this stream. In a simple sentence, the map returns the transformed object value.
    Filter is used for filtering the data, it always returns the boolean value. If it returns true, the item is added to list else it is filtered out (ignored)
  2. Find the Sum of Nos in List?
    public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers =  Arrays.asList(1,2,3,4,5,6,7,8,9);
    
            Integer sum = arrNumbers.stream().reduce(0, Integer::sum);
            Integer sum2 = arrNumbers.stream().reduce(0, (x,y)->x+y);
            Integer sum3 = arrNumbers.stream().reduce(0, ReduceEgs::sumNos);
        }
    
        public static Integer sumNos(int x, int y){
            System.out.println("a -"+ x + " b - "+  y);
            return x+y;
        }
    }
    
  3. Find the Min and Max in Nos List?
     public static void main(String[] args) {
            List<Integer> arrNumbers =  Arrays.asList(1,2,3,4,5,-6,7,-8,9);
    
            arrNumbers =  Arrays.asList(-5,-6,-8);
            Integer Max = arrNumbers.stream().reduce(0, (x,y)->x>y?x:y);
            System.out.println("Output 1 ="+ Max);
    
            //Its good to start with Identity as Negative Value in order to address negative Integer Comparison
            //If you start with 0 then 0 would be returned like above
            Integer Max2 = arrNumbers.stream().reduce(Integer.MIN_VALUE, (x,y)->x>y?x:y);
            System.out.println("Output 2 ="+ Max2);
    
            arrNumbers =  Arrays.asList(5,6,8);
    
            Integer Min = arrNumbers.stream().reduce(0, (x,y)->x<y?x:y);
            System.out.println("Output 3 ="+ Min);
    
            //Its good to start with Identity as Max Positive Value in order to address Integer Comparison
            Integer Min2 = arrNumbers.stream().reduce(Integer.MAX_VALUE, (x,y)->x<y?x:y);
            System.out.println("Output 4 ="+ Min2);
        }
    

    Output

    Output 1 =0
    Output 2 =-5
    Output 3 =0
    Output 4 =5
    
  4. Find the Sum of Square of Nos in List?
    public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers =  Arrays.asList(1,2,3);
    
            Integer squareSum = arrNumbers.stream().map(x->x*x).reduce(0, Integer::sum);
            System.out.println("Output 1 ="+ squareSum);
        }
    }
    

    Output

    Output 1 =14
    
  5. Find the Sum of Odd Nos in List?
    public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers =  Arrays.asList(1,2,3);
    
            Integer squareSum = arrNumbers.stream().filter(x-> x%2==1).reduce(0, Integer::sum);
            System.out.println("Output 1 ="+ squareSum);
        }
    }
    

    Output

    Output 1 =4
    
  6. Distinct elements from List?
    public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers =  Arrays.asList(1,2,3,1,4,2);
    
            System.out.println("Output 1");
            arrNumbers.stream().distinct().forEach(System.out::println);
        }
    }
    
    

    Output

    Output 1
    1
    2
    3
    4
    
  7. Sort Elements in List?
     public static void main(String[] args) {
       List<Integer> arrNumbers =  Arrays.asList(1,2,3,1,4,2);
    
       System.out.println("Output 2");
       arrNumbers.stream().sorted().forEach(System.out::println);
    }
    

    Output

    Output 2
    1
    1
    2
    2
    3
    4
    
  8. Sorting based on Natural Order, Reverse Order and Length of String?
    public static void main(String[] args) {
            List<String> arrSkills = Arrays.asList("Java", "Python",  "Ant");
    
            System.out.println("Sorting in Natural Order");
            arrSkills.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);
    
            System.out.println("Sorting in Reverse Order");
            arrSkills.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
    
            System.out.println("Sorting based on Length");
            arrSkills.stream().sorted(Comparator.comparing(str -> str.length())).forEach(System.out::println);
    
        }
    

    Output

    Sorting in Natural Order
    Ant
    Java
    Python
    Sorting in Reverse Order
    Python
    Java
    Ant
    Sorting based on Length
    Ant
    Java
    Python
    
  9. Collect the Elements in the List – Collect Even Nos and Length of String ?
        public static void main(String[] args) {
            List<Integer> arrNumbers = Arrays.asList(1,2,4,5,7,6);
            List<String> arrNames = Arrays.asList("Java", "Oracle", "Maven", "Ant");
    
            System.out.println("Collect Even Nos in list");
            List<Integer> arrEvenNumbers = arrNumbers.stream().filter(x->x%2==0).collect(Collectors.toList());
            arrEvenNumbers.stream().forEach(System.out::println);
    
            System.out.println("Length of Elements");
            List<Integer> arrEvenNumbers2 = arrNames.stream().map(str->str.length()).collect(Collectors.toList());
            arrEvenNumbers2.stream().forEach(System.out::println);
        }
    

    Output

    Collect Even Nos in list
    2
    4
    6
    Length of Elements
    4
    6
    5
    3
    
  10. Find the Sum of Nos in List?
    
    
  11. Find the Sum of Nos in List?
    
    
  12. Find the Sum of Nos in List?
    
    

When to use Lambda Expressions
If you have a functional Interface which has only one Abstract Method then we can use Lambda Expression at the Class which implements the functional interface
and provides the method definition for the abstract method in Functional Interface.

  1. We have a Function Interface Printable with abstract method print
  2. Two classes implements functional Interface – DailyReport and MothlyReport. They provide the method definition for Print() method in Printable Interface
  3. GenerateReport class has a downloadReport method which takes the Printable Type as argument
  4. DownloadReport Class we use the Printable reference and call the exact implementation by passing as argument

Printable.java

@FunctionalInterface
public interface Printable {
    public void print();
}

DailyReport.java

public class DailyReport implements Printable {
    @Override
    public void print() {
        System.out.println("Printing Report in Daily Format");
    }
}

MonthlyReport.java

public class MonthlyReport implements Printable {
    @Override
    public void print() {
        System.out.println("Printing Report in Monthly Format");
    }
}

GenerateReport.java

public class GenerateReport {
    public static void downloadReport(Printable printable){
        printable.print();
    }
}

Without Lambda Expression
DownloadReport.java

public class DownloadReport {
    public static void main(String[] args) {
        GenerateReport objGenReport = new GenerateReport();
        Printable objDailyReport = new DailyReport();
        Printable objMonthlyReport = new MonthlyReport();

        objGenReport.downloadReport(objDailyReport);
        objGenReport.downloadReport(objMonthlyReport);
    }
}

Output

Printing Report in Daily Format
Printing Report in Monthly Format

With Lambda Expression
Using Lambda expression we can pass method definition directly as parameter instead of using a class to extend interface and writing a method definition for the interface method
DownloadReport.java

public class DownloadReport {
    public static void main(String[] args) {
        GenerateReport objGenReport = new GenerateReport();
        objGenReport.downloadReport(() -> System.out.println("Printing Report in Daily Format using Lambda Expr"));

        Printable mnthlyReport = () -> System.out.println("Printing Report in Monthly Format using Lambda Expr");
        objGenReport.downloadReport(mnthlyReport);     
    }
}

Output

Printing Report in Daily Format using Lambda Expr
Printing Report in Monthly Format using Lambda Expr
With Lambda Expression Without Lambda Expression
We pass the method definition as Argument
  1. Method Definition in MonthlyReport.java
  2. Method Definition in DailyReport.java
  3. Object creation for DailyReport and MonthlyReport

Let’s take the below Example where we find the square of even numbers

ReduceEgs.java

public class ReduceEgs {
    public static void main(String[] args) {
        List<Integer> arrNumbers = Arrays.asList(1,2,4,5,7,6);

        arrNumbers.stream()
                  .filter(x-> x%2==0)
                  .map(x->x*x)
                  .forEach(System.out::println);
    }
}

In the above code we have

  1. Filter which takes Predicate as Param
  2. Map which takes Function as Param
  3. Sysout which takes Consumer as Param

The filter, Consumer and sysout have been expanded from the above code as below.

public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers = Arrays.asList(1,2,4,5,7,6);
    
            arrNumbers.stream()
                      .filter(getIntegerPredicate())
                      .map(getFunction())
                      .forEach(getPrintln());
        }
    
        private static Consumer<Integer> getPrintln() {
            return System.out::println;
        }
    
        private static Function<Integer, Integer> getFunction() {
            return x -> x * x;
        }
    
        private static Predicate<Integer> getIntegerPredicate() {
            return x -> x % 2 == 0;
        }
}

The Above code is refactored as below

public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers = Arrays.asList(1,2,4,5,7,6);

            Function<Integer, Integer> getSquare = x -> x * x;
            Predicate<Integer>         getEvenNo = x -> x % 2 == 0;
            Consumer<Integer>          showNos   = System.out::println;

            arrNumbers.stream()
                      .filter(getEvenNo)
                      .map(getSquare)
                      .forEach(showNos);
        }
}

What the java compiler does internally changes the above code as below. It created an anonymous inner class for implementing the methods in the interface.

  1. Function has apply method which should be implemented
  2. Consumer has accept method which should be implemented
  3. Predicate has test method which should be implemented
public class ReduceEgs {
        public static void main(String[] args) {
            List<Integer> arrNumbers = Arrays.asList(1,2,4,5,7,6);

            Function<Integer, Integer> getSquare = new Function<Integer, Integer>() {
                @Override
                public Integer apply(Integer integer) {
                    return integer * integer;
                }
            };

            Predicate<Integer> getEvenNo =  new Predicate<Integer>() {
                @Override
                public boolean test(Integer integer) {
                    return  integer % 2 == 0;
                }
            };

            Consumer<Integer> showNos = new Consumer<Integer>() {
                @Override
                public void accept(Integer integer) {
                    System.out.println(integer);
                }
            };

            arrNumbers.stream()
                      .filter(getEvenNo)
                      .map(getSquare)
                      .forEach(showNos);
        }
}

The output of all the above code is one and same as below

4
16
36

We have a Employee Object with following fields – empId,salary,empAge,totalExp,empName,location,pincode. Now we are going to do following operations in the List containing Employee Object.

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class MatchEgs {
    public static void main(String[] args) {
        Employee objEmp1 = new Employee(101, "Mugil", 30, "Chennai", "600018", 5, 5000);
        Employee objEmp2 = new Employee(102, "Mani", 33, "Chennai", "600028", 4, 6000);
        Employee objEmp3 = new Employee(103, "Madhu", 32, "Chennai", "600054", 6, 10000);
        Employee objEmp4 = new Employee(104, "Vinu", 29, "Bangalore", "500234", 5, 15000);
        Employee objEmp5 = new Employee(105, "Srini", 40, "Delhi", "622142", 10, 7000);
        Employee objEmp6 = new Employee(106, "Dimple", 35, "Delhi", "622142", 5, 8000);

        List<Employee> arrEmployee = Arrays.asList(objEmp1, objEmp2, objEmp3, objEmp4, objEmp5, objEmp6);
 }
}

Check if people match the criteria by passing predicate to anyMatch, noneMatch and allMatch

        Predicate<Employee> empAgeGreaterThan35 = employee -> employee.getEmpAge() > 35;
        Predicate<Employee> empAgeGreaterThan30 = employee -> employee.getEmpAge() > 30;
        Predicate<Employee> empStayingInDelhi   = employee -> employee.getLocation().equals("Delhi");

        //Check if Some Employee Age is Greater 30
        System.out.println(arrEmployee.stream().anyMatch(empAgeGreaterThan35));

        //Check if all Employee Age is above 30
        System.out.println(arrEmployee.stream().allMatch(empAgeGreaterThan30));

        //Check if any Employee Location in Delhi
        System.out.println(arrEmployee.stream().noneMatch(empStayingInDelhi));

Sort Employee by Name passing Comparator using Sorted and reversed. Sort based on more than
one field using thenComparing

        Comparator<Employee> sortByName = Comparator.comparing(Employee::getEmpName);
        List<Employee> arrSortedByName = arrEmployee.stream()
                                                    .sorted(sortByName)
                                                    .collect(Collectors.toList());
        System.out.println("----------------Sorted By Name----------------");
        arrSortedByName.forEach(employee -> System.out.println(employee));

        Comparator<Employee> sortByNameReversed = Comparator.comparing(Employee::getEmpName).reversed();
        List<Employee> arrSortedByNameRev = arrEmployee.stream()
                                                       .sorted(sortByNameReversed)
                                                       .collect(Collectors.toList());
        System.out.println("----------------Sorted By Name Reversed----------------");
        arrSortedByNameRev.forEach(employee -> System.out.println(employee));

        Comparator<Employee> sortByNameanAge = Comparator.comparing(Employee::getEmpName).thenComparing(Employee::getEmpAge);
        List<Employee> arrSortedByNameAge = arrEmployee.stream()
                                                       .sorted(sortByNameanAge)
                                                       .collect(Collectors.toList());
        System.out.println("----------------Sorted By Name and Age----------------");
        arrSortedByNameAge.forEach(employee -> System.out.println(employee));

Limit and Skip records using limit and skip

        System.out.println("----------------Limit Records to 3 ----------------");
        List<Employee> arrEmp3 =  arrEmployee.stream()
                                             .limit(3)
                                             .collect(Collectors.toList());
        arrEmp3.forEach(employee -> System.out.println(employee));

        System.out.println("----------------Skip First 3 Records ----------------");
        List<Employee> arrEmp4 =  arrEmployee.stream()
                                             .skip(3)
                                             .collect(Collectors.toList());
        arrEmp4.forEach(employee -> System.out.println(employee));

doWhile when condition is true using takeWhile and vice-versa(Until condition is met) using dropWhile

        System.out.println("----------------Print Records until Chennai - Keeps printing until condition is True----------------");
        List<Employee> arrEmp5 =  arrEmployee.stream()
                                             .takeWhile(employee -> employee.getLocation().equals("Chennai"))
                                             .collect(Collectors.toList());
        arrEmp5.forEach(employee -> System.out.println(employee));

        System.out.println("----------------Print Records until Chennai - Stops printing when condition is Met ----------------");
        List<Employee> arrEmp6 =  arrEmployee.stream()
                                             .dropWhile(employee -> employee.getLocation().equals("Bangalore"))
                                             .collect(Collectors.toList());
        arrEmp5.forEach(employee -> System.out.println(employee));

Get Minimum and Maximum age of employee using min and max. The List should be sorted first using comparator. Similarly get Employee with max experience in a particular location using predicate, comparator and max.

        System.out.println("----------------Record with Min and Max Value ----------------");
        Comparator<Employee> sortByAge = Comparator.comparing(Employee::getEmpAge);
        List<Employee> arrEmp7 =  arrEmployee.stream()
                                             .min(sortByAge)
                                             .stream()
                                             .collect(Collectors.toList());
        List<Employee> arrEmp8 =  arrEmployee.stream()
                                             .max(sortByAge)
                                             .stream()
                                             .collect(Collectors.toList());
        arrEmp7.forEach(employee -> System.out.println(employee));
        arrEmp8.forEach(employee -> System.out.println(employee));

        System.out.println("----------------Get Employee with Max Exp in Chennai ----------------");
        Predicate<Employee> filterEmpInChennai = employee -> employee.getLocation().equals("Chennai");
        Comparator<Employee> sortByExp = Comparator.comparing(Employee::getTotalExp);
        List<Employee> arrEmp11 =  arrEmployee.stream()
                                              .filter(filterEmpInChennai)
                                              .max(sortByExp)
                                              .stream()
                                              .collect(Collectors.toList());
        arrEmp11.forEach(employee -> System.out.println(employee));

FindFirst Employee who matches criteria and FindAny who matches criteria. Both takes predicate as input.

findAny – Returns an Optional describing some element of the stream, or an empty Optional if the stream is empty.it is free to select any element in the stream. This is to allow for maximal performance in parallel operations;

        System.out.println("----------------Find First  ----------------");
        Predicate<Employee> empStayingInChennai = employee -> employee.getLocation().equals("Chennai");
        List<Employee> arrEmp9 =  arrEmployee.stream()
                                             .filter(empStayingInChennai)
                                             .findFirst()
                                             .stream()
                                             .collect(Collectors.toList());

        arrEmp9.forEach(employee -> System.out.println(employee));

        System.out.println("----------------Find Any  ----------------");
        List<Employee> arrEmp10 =  arrEmployee.stream()
                                              .filter(empAgeGreaterThan30)
                                              .findAny()
                                              .stream()
                                              .collect(Collectors.toList());
        arrEmp10.forEach(employee -> System.out.println(employee));

Get the Sum of salary of Employees in Location(Chennai) using sum

         System.out.println("----------------Sum - Get Sum of Salary in Chennai  ----------------");
        //Method 1
        Integer empTotalSalaryInChennai1 = arrEmployee.stream()
                                                      .filter(empStayingInChennai)
                                                      .map(employee -> employee.getSalary())
                                                      .collect(Collectors.toList())
                                                      .stream()
                                                      .reduce(0, Integer::sum);
        System.out.println("Sum of Empl Salary - Chennai " + empTotalSalaryInChennai1);

        //Method 2
        Integer empTotalSalaryInChennai2 = arrEmployee.stream()
                                                      .filter(empStayingInChennai)
                                                      .mapToInt(Employee::getSalary).sum();
        System.out.println("Sum of Empl Salary - Chennai " + empTotalSalaryInChennai2);

        //Method 3
        Integer empTotalSalaryInChennai3 = arrEmployee.stream() 
                                                      .filter(empStayingInChennai)
                                                      .map(employee -> employee.getSalary())
                                                      .collect(Collectors.summingInt(Integer::intValue));
        System.out.println("Sum of Empl Salary - Chennai " + empTotalSalaryInChennai3);

Get Max and Min salary of employee

  Comparator<Employee> cmpSalComp = (Employee objEmpp1, Employee objEmpp2) -> Double.compare(objEmpp1.Salary,objEmpp2.Salary);

  Employee empMaxObj = arrEmpl.stream()
                                   .max(cmpSalComp)
                                   .get();
                                   
  Employee empMinObj =   arrEmpl.stream()
                                 .min(cmpSalComp)
                                 .get();

Get Average salary of employee grouped by Location

        System.out.println("----------------Average - Grouped by Location  ----------------");
        Map<String, Double> arrAvgSalByLoc =  arrEmpl.stream()
                                                     .collect(Collectors.groupingBy(Employee::getLocation, Collectors.averagingDouble(Employee::getSalary)));
                                         
        System.out.print(arrAvgSalByLoc);

Get Average salary of employee in location using average

        System.out.println("----------------Average - Get Average of Salary in Chennai  ----------------");
        OptionalDouble empAvgSalaryInChennai = arrEmployee.stream()
                                                          .filter(empStayingInChennai)
                                                          .mapToInt(Employee::getSalary)
                                                          .average();
        System.out.println("Average Salary of Employee - Chennai " + empAvgSalaryInChennai);

Get List of Employees in a location using groupingBy. The Return type is hashmap with location as key and List of employees in value

        System.out.println("----------------Group By -  Get Employees grouped by Location  ----------------");
        Map<String, List<Employee>> hmEmp13 = arrEmployee.stream()
                                                         .collect(Collectors.groupingBy(Employee::getLocation));
        hmEmp13.forEach((s, employees) -> {
            System.out.println("Employees from "+ s);
            employees.forEach(employee -> System.out.println(employee));
        });

Get Employee grouped by Location and getting Maximum Salary groupingBy and maxBy

        System.out.println("----------------Group By -  Get Employees with Max Salary in Location  ----------------");
        Comparator<Employee> cmprSalary = Comparator.comparing(Employee::getSalary);
        Map<String, Optional<Employee>> hmEmp14 = arrEmployee.stream()
                                                             .collect(Collectors.groupingBy(Employee::getLocation, Collectors.maxBy(cmprSalary)));

        hmEmp14.forEach((s, employee) -> {
            System.out.print("Employee Location is "+ s +  " and salary is ");
            employee.ifPresent(emp -> System.out.println(emp.getSalary()));
        });

Get Employee Name using mapping grouped by Location using groupingBy

        System.out.println("---------------- Employee at Location -  Using Collectors.mapping  ----------------");
        Map<String, List<String>> hmEmp16 = arrEmployee.stream()
                                                       .collect(Collectors.groupingBy(Employee::getLocation, Collectors.mapping(Employee::getEmpName, Collectors.toList())));

        hmEmp16.forEach((s, employee) -> {
            System.out.println("Employee Name is " + employee + " and Location is "+ s );

        });

Output

true
false
false
----------------Sorted By Name----------------
Employee{empId=106, empName='Dimple', empAge=35, totalExp=5, location='Delhi', pincode='622142', salary=8000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
----------------Sorted By Name Reversed----------------
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
Employee{empId=106, empName='Dimple', empAge=35, totalExp=5, location='Delhi', pincode='622142', salary=8000}
----------------Sorted By Name and Age----------------
Employee{empId=106, empName='Dimple', empAge=35, totalExp=5, location='Delhi', pincode='622142', salary=8000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
----------------Limit Records to 3 ----------------
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
----------------Skip First 3 Records ----------------
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
Employee{empId=106, empName='Dimple', empAge=35, totalExp=5, location='Delhi', pincode='622142', salary=8000}
----------------Print Records until Chennai - Keeps printing until condition is True----------------
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
----------------Print Records until Chennai - Stops printing when condition is Met ----------------
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
----------------Record with Min and Max Value ----------------
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
----------------Get Employee with Max Exp in Chennai ----------------
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
----------------Find First  ----------------
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
----------------Find Any  ----------------
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
----------------Sum - Get Sum of Salary in Chennai  ----------------
Sum of Empl Salary - Chennai 21000
Sum of Empl Salary - Chennai 21000
Sum of Empl Salary - Chennai 21000
----------------Average - Get Average of Salary in Chennai  ----------------
Average Salary of Employee - Chennai OptionalDouble[7000.0]
----------------Group By -  Get Employees grouped by Location  ----------------
Employees from Delhi
Employee{empId=105, empName='Srini', empAge=40, totalExp=10, location='Delhi', pincode='622142', salary=7000}
Employee{empId=106, empName='Dimple', empAge=35, totalExp=5, location='Delhi', pincode='622142', salary=8000}
Employees from Chennai
Employee{empId=101, empName='Mugil', empAge=30, totalExp=5, location='Chennai', pincode='600018', salary=5000}
Employee{empId=102, empName='Mani', empAge=33, totalExp=4, location='Chennai', pincode='600028', salary=6000}
Employee{empId=103, empName='Madhu', empAge=32, totalExp=6, location='Chennai', pincode='600054', salary=10000}
Employees from Bangalore
Employee{empId=104, empName='Vinu', empAge=29, totalExp=5, location='Bangalore', pincode='500234', salary=15000}
----------------Group By -  Get Employees with Max Salary in Location  ----------------
Employee Location is Delhi and salary is 8000
Employee Location is Chennai and salary is 10000
Employee Location is Bangalore and salary is 15000
---------------- Employee at Location -  Get Employee Object grouped by Location  ----------------
Employee Location is Delhi
Srini
Dimple
Employee Location is Chennai
Mugil
Mani
Madhu
Employee Location is Bangalore
Vinu
---------------- Employee at Location -  Using Collectors.mapping  ----------------
Employee Name is [Srini, Dimple] and Location is Delhi
Employee Name is [Mugil, Mani, Madhu] and Location is Chennai
Employee Name is [Vinu] and Location is Bangalore

How to find the Stream is Primitive or Wrapper?

public class PrimStreams {
    public static void main(String[] args) {
        List<Integer> arrReviews1 = List.of(8, 9, 8 , 6, 2, 7, 6);
        System.out.println(arrReviews1.stream());

        int[] arrReviews2 = {8, 9, 8 , 6, 2, 7, 6};
        System.out.println(Arrays.stream(arrReviews2));

        System.out.println("----------Wrapper Streams Starts----------");
        System.out.println(arrReviews1.stream().reduce(Integer::sum));
        System.out.println(arrReviews1.stream().reduce(Integer::min));
        System.out.println(arrReviews1.stream().reduce(Integer::max));
        System.out.println(arrReviews1.stream().mapToInt(Integer::intValue).average().orElse(Double.NaN));

        System.out.println("----------Primitive Streams Starts----------");
        System.out.println(Arrays.stream(arrReviews2).sum());
        System.out.println(Arrays.stream(arrReviews2).min());
        System.out.println(Arrays.stream(arrReviews2).max());
        System.out.println(Arrays.stream(arrReviews2).average());
    }
}

In the below output you may see the wrapper class stream would be printed as ReferencePipeline whereas primitive stream would be displayed as IntPipeline. The above code contains doing some basic operation in both primitive and wrapper stream. If the stream is primitive type it would be easy to carry out operation over stream and more memory efficient since there would be no boxing and unboxing involved
Output

java.util.stream.ReferencePipeline$Head@1d251891
java.util.stream.IntPipeline$Head@2133c8f8
----------Wrapper Streams Starts----------
Optional[46]
Optional[2]
Optional[9]
6.571428571428571
----------Primitive Streams Starts----------
46
OptionalInt[2]
OptionalInt[9]
OptionalDouble[6.571428571428571]

How to create a primitive stream of Int?

  IntStream arrReviews3 = IntStream.of(8, 9, 8 , 6, 2, 7, 6);

Basic Operations in primitive stream of Int?

  1. range – Considers nos in specific range.range doesnot takes upper limit no.To address that rangeClosed is used
  2. iterate– Iterate helps in iterating till a particular range by taking Initial and urnaryOperator as params. The default behaviour of Iterator is endless loop.To stop after particular limit like one we did in range we should use limit function.
  3. peek– peek helps going through the values in stream without making any changes. This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline
  4. boxed– boxed will help in boxing primitive to wrapper while collecting the values in to a list.
IntStream arrReviews = IntStream.of(8, 9, 8 , 6, 2, 7, 6);

        System.out.println("----------Stream printing Range of no ----------");

        System.out.println("range doesnot include last no 10 so sum is from 1 to 9 - " + IntStream.range(0,10).sum());
        System.out.println("rangeClosed would include last no 1 to 10 - " + IntStream.rangeClosed(0,10).sum());

        System.out.println("Using Peek we can look at nos in stream");
        System.out.println(IntStream.rangeClosed(0,10)
                                    .peek(System.out::println)
                                    .sum());

        System.out.println("iterate helps in iterating till a specific value");
        System.out.println(IntStream.iterate(1,e->e+1)
                                    .peek(System.out::println)
                                    .limit(10)
                                    .sum());

        System.out.println("iterate helps in iterating till a specific value");
        System.out.println(IntStream.iterate(1,e->e+2)
                                    .peek(System.out::println)
                                    .limit(10)
                                    .sum());

        System.out.println("Iterating over range and collecting in a list");
        List<Integer> arrIntegStream = IntStream.iterate(1,e->e+1)
                                                .peek(System.out::println)
                                                .limit(10)
                                                .boxed()
                                                .collect(Collectors.toList());
        System.out.println(arrIntegStream);

Finding Factorial of No of range 5

public class Factorial {
    public static void main(String[] args) {
        System.out.println(IntStream.rangeClosed(1,5).reduce(1, (x,y)->x*y));

        int i,fact=1;
        int number=5;//It is the number to calculate factorial
        for(i=1;i<=number;i++){
            fact=fact*i;
        }
        System.out.println(fact);
    }
}

Find Maximum of Number in Stream

class HelloWorld {
    public static void main(String[] args) {
       List<Integer> arrNum  = List.of(3,4,5,2,1);
       
       Integer max = arrNum.stream()
                           .max(Integer::compare)
                           .get();
                            
       System.out.print(max);
    }
}

Output

5

The same when used in Objects comparator should be used as one below.

Find Maximum of Number in Stream

.
.       
      Comparator<Employee> sortByExp = Comparator.comparing(Employee::getTotalExp);
      List<Employee> arrEmp11 =  arrEmployee.stream()      
                                            .max(sortByExp);
.
.

Basic String Operation in Streams

  1. Join strings in a list using Collectors.joining
  2. Make Array of String using split. Split would return Array Stream
  3. Apply transformation in elements in list using replaceAll
  4. To flatten the Array Stream in the List use flatMap
  5. Get rid of particular element in List of String using removeIf
  6. Use replaceAll to carry Out Transformation similar to map()
  7. Use peek to support debugging, where you want to see the elements as they flow past a certain point in a pipeline
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StringOps {
    public static void main(String[] args) {
        List<String> arrSkills = List.of("Core Java", "Core Spring", "Spring MVC", "Spring Security", "Spring Boot", "RestAPI");

        System.out.println("-----Join Elements in List using Seperator - [,]---------");
        String skills = arrSkills.stream().collect(Collectors.joining(","));
        System.out.println("Skills are "+ skills);

        System.out.println("-----Split String and Store in String Array---------");
        String[] arrSkillList = skills.split(",");
        Arrays.stream(arrSkillList).forEach(System.out::println);

        System.out.println("-----Convert to UpperCase---------");
        List<String> arrModSkills = new ArrayList(arrSkills);
        arrModSkills.replaceAll(str -> str.toUpperCase());
        arrModSkills.forEach(System.out::println);

        System.out.println("-----Using flatMap to flatten Arrays of Stream and Getting Distinct Location---------");
        List<String> arrLocation = List.of("Madras,Mumbai,Bangalore", "Madras,Kolkatta,Delhi", "Delhi,Madras");
        List allLocations = arrLocation.stream()
                                       .map(location->location.split(","))
                                       .peek(loc-> System.out.println(loc))
                                       .flatMap(Arrays::stream)
                                       .distinct()
                                       .collect(Collectors.toList());

        allLocations.stream()
                    .map(loc->replaceMadras(loc.toString()))
                    .forEach(System.out::println);

        System.out.println("------------Remove Particular Location using removeIf------------");
        allLocations.removeIf(loc->loc.equals("Delhi"));
        allLocations.forEach(loc-> System.out.println(loc));
    }

    private static String replaceMadras(String location){
        if(location.equals("Madras"))
            return "Chennai";
        else
            return location;
    }
}

Converting Multiple List into Single List

public class StreamMisc {
    public static void main(String[] args) {
        List<List<String>> arrSkills = new ArrayList<>();
        arrSkills.add(Arrays.asList("Java", "C++", "C#"));
        arrSkills.add(Arrays.asList("HTML", "CSS"));
        arrSkills.add(Arrays.asList("SQL", "MySQL"));
        arrSkills.add(Arrays.asList("Azure"));

        List<String> arrAllSkills = arrSkills.stream().reduce(new ArrayList<>(), (a, b) -> {
            a.addAll(b);
            return a;
        });

        arrAllSkills.forEach((skill) -> System.out.print(skill + ","));
    }
}

Output

Java,C++,C#,HTML,CSS,SQL,MySQL,Azure